/* Copyright (C) 2001-2023 Artifex Software, Inc. All Rights Reserved. This software is provided AS-IS with no warranty, either express or implied. This software is distributed under license and may not be copied, modified or distributed except as expressly authorized under the terms of the license contained in the file LICENSE in this distribution. Refer to licensing information at http://www.artifex.com or contact Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, CA 94129, USA, for further information. */ /* pcparse.c */ /* PCL5 parser */ #include "stdio_.h" #include "gdebug.h" #include "gstypes.h" #include "scommon.h" #include "pcparse.h" #include "pcstate.h" /* for display_functions */ #include "pcursor.h" #include "rtmisc.h" #include "rtgmode.h" #include "rtraster.h" /* We don't know whether an Enable Display Functions takes effect while */ /* defining a macro. To play it safe, we're providing both options. */ /* If the following #define is uncommented, E.D.F. *does* take effect */ /* while scanning a macro definition. */ /*#define DISPLAY_FUNCTIONS_IN_MACRO*/ /* ---------------- Command definition ---------------- */ /* Register a command. Return true if this is a redefinition. */ static bool pcl_register_command(byte * pindex, const pcl_command_definition_t * pcmd, pcl_parser_state_t * pcl_parser_state) { int index = pcl_parser_state->definitions->pcl_command_next_index; byte prev = *pindex; if (prev != 0 && prev <= index && pcl_parser_state->definitions->pcl_command_list[prev] == pcmd) index = prev; else if (index != 0 && pcl_parser_state->definitions->pcl_command_list[index] == pcmd); else pcl_parser_state->definitions-> pcl_command_list [pcl_parser_state->definitions->pcl_command_next_index = ++index] = (pcl_command_definition_t *) pcmd; *pindex = index; return (prev != 0 && prev != index); } /* Define a command or list of commands. */ void pcl_define_control_command(int /*char */ chr, const pcl_command_definition_t * pcmd, pcl_parser_state_t * pcl_parser_state) { #ifdef DEBUG if (chr < 0 || chr >= countof(pcl_parser_state->definitions->pcl_control_command_indices)) if_debug1('I', "Invalid control character %d\n", chr); else if ( #endif pcl_register_command(&pcl_parser_state-> definitions->pcl_control_command_indices [chr], pcmd, pcl_parser_state) #ifdef DEBUG ) if_debug1('I', "Redefining control character %d\n", chr); #endif ; } void pcl_define_escape_command(int /*char */ chr, const pcl_command_definition_t * pcmd, pcl_parser_state_t * pcl_parser_state) { #ifdef DEBUG if (chr < min_escape_2char || chr > max_escape_2char) if_debug1('I', "Invalid escape character %c\n", chr); else if ( #endif pcl_register_command(&pcl_parser_state-> definitions->pcl_escape_command_indices [chr - min_escape_2char], pcmd, pcl_parser_state) #ifdef DEBUG ) if_debug1('I', "Redefining ESC %c\n", chr) #endif ; } /* * Convert escape classes to second level dispatch indices. */ static const byte pcl_escape_class_indices[max_escape_class - min_escape_class + 1] = { 0, 0, 0, 0, 1 /*% */ , 2 /*& */ , 0, 3 /*( */ , 4 /*) */ , 5 /***/ , 0, 0, 0, 0, 0 }; void pcl_define_class_command(int /*char */ class, int /*char */ group, int /*char */ command, const pcl_command_definition_t * pcmd, pcl_parser_state_t * pcl_parser_state) { #ifdef DEBUG if (class < min_escape_class || class > max_escape_class || pcl_escape_class_indices[class - min_escape_class] == 0 || (group != 0 && (group < min_escape_group || group > max_escape_group)) || command < min_escape_command || command > max_escape_command) if_debug3('I', "Invalid command %c %c %c\n", class, group, command); else if ( #endif pcl_register_command(&pcl_parser_state-> definitions->pcl_grouped_command_indices [pcl_escape_class_indices [class - min_escape_class] - 1] [group == 0 ? 0 : group - min_escape_group + 1] [command - min_escape_command], pcmd, pcl_parser_state) #ifdef DEBUG ) if_debug3('I', "Redefining ESC %c %c %c\n", class, (group == 0 ? ' ' : group), command) #endif ; } void pcl_define_class_commands(int /*char */ class, const pcl_grouped_command_definition_t * pgroup, pcl_parser_state_t * pcl_parser_state) { const pcl_grouped_command_definition_t *pgc = pgroup; for (; pgc->command != 0; ++pgc) pcl_define_class_command(class, pgc->group, pgc->command, &pgc->defn, pcl_parser_state); } /* * Look up an escape command. The arguments are as follows: * ESC x 0, 0, 'x' * ESC ? c '?', 0, 'c' * ESC ? b c '?', 'b', 'c' * The caller is responsible for providing valid arguments. */ static const pcl_command_definition_t * pcl_get_command_definition(pcl_parser_state_t * pcl_parser_state, int /*char */ class, int /*char */ group, int /*char */ command) { const pcl_command_definition_t *cdefn = 0; if (class == 0) { if (command >= min_escape_2char && command <= max_escape_2char) cdefn = pcl_parser_state->definitions->pcl_command_list [pcl_parser_state-> definitions->pcl_escape_command_indices[command - min_escape_2char]]; } else { int class_index = pcl_escape_class_indices[class - min_escape_class]; if (class_index) cdefn = pcl_parser_state->definitions->pcl_command_list [pcl_parser_state-> definitions->pcl_grouped_command_indices[class_index - 1] [group ? group - min_escape_group + 1 : 0] [command - min_escape_command] ]; } #ifdef DEBUG if (cdefn == 0) { if (class == 0) if_debug1('I', "ESC %c undefined\n", command); else if (group == 0) if_debug2('I', "ESC %c %c undefined\n", class, command); else if_debug3('I', "ESC %c %c %c undefined\n", class, group, command); } #endif return cdefn; } /* ---------------- Parsing ---------------- */ /* Initialize the parser state. */ int pcl_process_init(pcl_parser_state_t * pst, pcl_state_t * pcs) { pcl_args_t args; int code = 0; pst->scan_type = scanning_none; pst->args.data = 0; pst->args.data_on_heap = false; pst->short_hand = false; pst->min_bytes_needed = 0; /* * RTL Files should start with a PCL escape sequence to enter the * GL/2 parser but many do not, so we start in HPGL. If an escape * sequence is detected the parse mode returns to PCL. */ if (pcs->personality == rtl) { arg_set_uint(&args, 0); code = rtl_enter_hpgl_mode(&args, pcs); } return code; } /* Adjust the argument value according to the command's argument type. */ /* Return 1 if the command should be ignored. */ static int pcl_adjust_arg(pcl_args_t * pargs, const pcl_command_definition_t * pdefn) { uint acts = pdefn->actions; if (value_is_neg(&pargs->value)) { switch (acts & pca_neg_action) { case pca_neg_clamp: arg_set_uint(pargs, 0); break; case pca_neg_error: return e_Range; case pca_neg_ignore: return 1; default: /* case pca_neg_ok */ ; } } else if (pargs->value.i > 32767) /* overflowed int range */ switch (acts & pca_big_action) { case pca_big_clamp: arg_set_uint(pargs, 32767); break; case pca_big_error: return e_Range; case pca_big_ignore: return 1; default: /* case pca_big_ok */ ; } return 0; } /* Append some just-scanned input data to the macro being defined. */ static int append_macro(const byte * from, const byte * to, pcl_state_t * pcs) { if (pcs->macro_definition != NULL) { uint count = to - from; uint size = gs_object_size(pcs->memory, pcs->macro_definition); byte *new_defn = gs_resize_object(pcs->memory, pcs->macro_definition, size + count, "append_macro"); if (new_defn == 0) return_error(e_Memory); memcpy(new_defn + size, from + 1, count); pcs->macro_definition = new_defn; } return 0; } #ifdef DEBUG static void debug_dump_char(gs_memory_t *mem, char chr) { dmprintf1(mem, (chr >= 33 && chr <= 126 ? "%c" : "\\%03o"), (unsigned char)chr); if (chr == '\\') dmprintf1(mem, "%c", '\\'); return; } #endif /* Process a buffer of PCL commands. */ int pcl_process(pcl_parser_state_t * pst, pcl_state_t * pcs, stream_cursor_read * pr) { const byte *p = pr->ptr; const byte *rlimit = pr->limit; int code = 0; int bytelen = 0; bool in_macro = pcs->defining_macro; /* Record how much of the input we've copied into a macro */ /* in the process of being defined. */ const byte *macro_p = p; /* Reset the parameter scanner */ #define avalue pst->args.value #define param_init()\ (avalue.type = pcv_none, avalue.i = 0) #ifdef DISPLAY_FUNCTIONS_IN_MACRO # define do_display_functions() 1 #else # define do_display_functions() (!in_macro) #endif if (rlimit - p < pst->min_bytes_needed) { pst->min_bytes_needed = 0; p = rlimit; goto x; } else { pst->min_bytes_needed = 0; } while (p < rlimit) { byte chr = 0x00; /* silence a compiler warning */ const pcl_command_definition_t *cdefn = NULL; switch (pst->scan_type) { case scanning_data: { /* Accumulate command data in a buffer. */ uint count = uint_arg(&pst->args); uint pos = pst->data_pos; if (pos < count) { uint avail = rlimit - p; uint copy = min(count - pos, avail); memcpy(pst->args.data + pos, p + 1, copy); pst->data_pos += copy; p += copy; continue; } /* Invoke the command. */ cdefn = pcl_get_command_definition(pst, pst->param_class, pst->param_group, pst->args.command); if (pst->short_hand) { pst->scan_type = scanning_parameter; continue; } else pst->scan_type = scanning_none; break; } case scanning_display: { /* Display, don't execute, all characters. */ chr = *++p; if (chr == ESC) { int index; if (p >= rlimit) goto x; if (p[1] >= min_escape_2char && p[1] <= max_escape_2char && (index = pst-> definitions->pcl_escape_command_indices[p[1] - min_escape_2char]) != 0 && pst->definitions-> pcl_command_list[index]->proc == pcl_disable_display_functions) { if (do_display_functions()) { pst->args.command = chr; code = pcl_plain_char(&pst->args, pcs); if (code < 0) goto x; } pst->args.command = chr = *++p; pcl_disable_display_functions(&pst->args, pcs); pst->scan_type = scanning_none; } } if (do_display_functions()) { if (chr == CR) { code = pcl_do_CR(pcs); if (code >= 0) code = pcl_do_LF(pcs); } else { pst->args.command = chr; code = pcl_plain_char(&pst->args, pcs); } if (code < 0) goto x; } continue; } case scanning_parameter: for (;;) { if (p >= rlimit) goto x; chr = *++p; /* * The parser for numbers is very lenient, and accepts * many strings that aren't valid numbers. If this * ever becomes a problem, we can tighten it up.... */ if (pst->garbage_in_parameter) { if (chr < ' ' || chr > '?') { pst->garbage_in_parameter = false; /* no garbage anymore */ break; } } else { if (chr >= '0' && chr <= '9') { chr -= '0'; if (value_is_float(&avalue)) avalue.fraction += (chr / (pst->scale *= 10)); else avalue.type |= pcv_int, avalue.i = avalue.i * 10 + chr; } else if (chr == '-') avalue.type |= pcv_neg; else if (chr == '+') avalue.type |= pcv_pos; else if (chr == '.') avalue.type |= pcv_float, avalue.fraction = 0, pst->scale = 1.0; else if (chr == ' ') continue; else if (chr >= ' ' && chr <= '?') { /* Ignore garbage nearby in the code space. */ /* skip the remainder until a chr outside this range is found */ if (avalue.type != pcv_none) pst->garbage_in_parameter = true; continue; } else break; } } #ifdef DEBUG if (gs_debug_c('i')) { dmprintf(pcs->memory, "ESC "); debug_dump_char(pcs->memory, pst->param_class); dmprintf(pcs->memory, " "); debug_dump_char(pcs->memory, pst->param_group); if (value_is_present(&avalue)) { dmputc(pcs->memory, ' '); if (value_is_signed(&avalue)) dmputc(pcs->memory, (value_is_neg(&avalue) ? '-' : '+')); if (value_is_float(&avalue)) dmprintf1(pcs->memory, "%g", avalue.i + avalue.fraction); else dmprintf1(pcs->memory, "%u", avalue.i); } dmprintf1(pcs->memory, " %c\n", chr); } #endif pst->short_hand = false; if (chr >= min_escape_command + 32 && chr <= max_escape_command + 32) { pst->short_hand = true; chr -= 32; } else if (chr >= min_escape_command && chr <= max_escape_command) pst->scan_type = scanning_none; else { pst->scan_type = scanning_none; /* Rescan the out-of-place character. */ --p; continue; } /* Dispatch on param_class, param_group, and chr. */ cdefn = pcl_get_command_definition(pst, pst->param_class, pst->param_group, chr); if (cdefn) { if_debug1m('i', pcs->memory, " [%s]\n", cdefn->cname); code = pcl_adjust_arg(&pst->args, cdefn); if (code < 0) goto x; if (cdefn->actions & pca_byte_data) { uint count = uint_arg(&pst->args); if ((count > 0) && (rlimit - p < count)) { pst->args.data = gs_alloc_bytes(pcs->memory, count, "command data"); if (pst->args.data == 0) { --p; code = gs_note_error(e_Memory); (void)pcl_grestore(pcs); goto x; } pst->args.data_on_heap = true; pst->args.command = chr; pst->data_pos = 0; pst->scan_type = scanning_data; continue; } pst->args.data = (byte *) (p + 1); pst->args.data_on_heap = false; p += count; } break; } param_init(); continue; case scanning_none: if (pcs->parse_other) { /* HPGL/2 mode */ pr->ptr = p; code = (*pcs->parse_other) (pcs->parse_data, pcs, pr); p = pr->ptr; if (code < 0 || (code == 0 && pcs->parse_other)) goto x; } chr = *++p; /* check for multi-byte scanning */ bytelen = pcl_char_bytelen(chr, pcs->text_parsing_method); if (bytelen == 0) { bytelen = 1; /* invalid utf-8 leading char */ } if (bytelen > 1) { /* check if we need more data */ if ((p + bytelen - 1) > rlimit) { pst->min_bytes_needed = 2; --p; goto x; } if_debug2m('i', pcs->memory, "%x%x\n", p[0], p[1]); code = pcl_text(p, bytelen, pcs, false); if (code < 0) goto x; /* now pass over the remaining bytes */ p += (bytelen - 1); cdefn = NULL; } else if (chr != ESC) { #ifdef DEBUG if (gs_debug_c('i')) { if_debug1m('i', pcs->memory, "[i%d] ", bytelen); debug_dump_char(pcs->memory, chr); if_debug0m('i', pcs->memory, "\n"); } #endif cdefn = pst->definitions->pcl_command_list [chr < 33 ? pst->definitions->pcl_control_command_indices[chr] : pst->definitions->pcl_control_command_indices[1]]; if ((cdefn == 0 || cdefn->proc == pcl_plain_char) && !in_macro && !pcs->parse_other && !pcs->raster_state.graphics_mode) { /* Look ahead for a run of plain text. We can be very conservative about this, because this is only a performance enhancement. */ const byte *str = p; while (p < rlimit && p[1] >= 32 && p[1] <= 127) { #ifdef DEBUG if (gs_debug_c('i')) { if (p == str) if_debug1m('i', pcs->memory, "[i%d] ", bytelen); debug_dump_char(pcs->memory, p[1]); } #endif ++p; } if_debug0m('i', pcs->memory, "\n"); code = pcl_text(str, (uint) (p + 1 - str), pcs, false); if (code < 0) goto x; cdefn = NULL; } } else { if (p >= rlimit) { pst->min_bytes_needed = 2; --p; goto x; } chr = *++p; if (chr < min_escape_class || chr > max_escape_class) { cdefn = pcl_get_command_definition(pst, 0, 0, chr); if (!cdefn) { /* Skip the ESC, back up to the char following the ESC. */ --p; #ifdef DEBUG if (gs_debug_c('i')) { debug_dump_char(pcs->memory, '\033'); } #endif continue; } #ifdef DEBUG if (gs_debug_c('i')) { dmprintf(pcs->memory, "ESC "); debug_dump_char(pcs->memory, chr); dmprintf(pcs->memory, "\n"); dmprintf1(pcs->memory, " [%s]\n", cdefn->cname); } #endif } else { if (p >= rlimit) { p -= 2; pst->min_bytes_needed = 3; goto x; } pst->param_class = chr; chr = *++p; if (chr < min_escape_group || chr > max_escape_group) { /* Class but no group */ --p; chr = 0; } pst->param_group = chr; pst->scan_type = scanning_parameter; pst->garbage_in_parameter = false; param_init(); continue; } } break; } if (cdefn == NULL) { param_init(); continue; } if (!in_macro || (cdefn->actions & pca_in_macro)) { /* This might be the end-of-macro command. */ /* Make sure all the data through this point */ /* has been captured in the macro definition. */ if (in_macro) { code = append_macro(macro_p, p, pcs); macro_p = p; if (code < 0) goto x; } pst->args.command = chr; if (!pcs->raster_state.graphics_mode || (cdefn->actions & pca_raster_graphics) || (code = pcl_end_graphics_mode_implicit(pcs, cdefn-> actions & pca_dont_lockout_in_rtl)) >= 0) { if ((pcs->personality != rtl) || ((pcs->personality == rtl) && (cdefn-> actions & pca_in_rtl))) code = (*cdefn->proc) (&pst->args, pcs); } /* * If we allocated a buffer for command data, * and the command didn't take possession of it, * free it now. */ if (pst->args.data_on_heap && pst->args.data) { gs_free_object(pcs->memory, pst->args.data, "command data"); pst->args.data = 0; } if (code == e_Unimplemented) { #if e_Unimplemented != 0 if_debug0m('i', pcs->memory, "Unimplemented\n"); #endif } else if (code < 0) break; if (pcs->display_functions) { /* This calls for a special parsing state. */ pst->scan_type = scanning_display; } if (pcs->defining_macro && !in_macro) { /* We just started a macro definition. */ if (pst->scan_type != scanning_none) { /* macro was defined with an incomple combined command */ /* start definition of macro with esc&f preloaded */ static const byte macro_prefix[4] = " \033&f"; code = append_macro(¯o_prefix[0], ¯o_prefix[3], pcs); if (code < 0) goto x; } macro_p = p; } in_macro = pcs->defining_macro; } else { /* * If we allocated a buffer for command data, * free it now. */ if (pst->args.data_on_heap && pst->args.data) { gs_free_object(pcs->memory, pst->args.data, "command data"); pst->args.data = 0; } } param_init(); } x:pr->ptr = p; /* Append the last bit of data to the macro, if defining. */ if (in_macro) { int mcode = append_macro(macro_p, p, pcs); if (mcode < 0 && code >= 0) code = mcode; } if (code < 0) { /* This avoids memento squeeze leaks. */ (void) pcl_complete_raster(pcs); } return code; } /* inialize the pcl command counter */ int pcl_init_command_index(pcl_parser_state_t * pcl_parser_state, pcl_state_t * pcs) { pcl_command_definitions_t *definitions = (pcl_command_definitions_t *) gs_alloc_bytes(pcs->memory, sizeof (pcl_command_definitions_t), "pcl_init_command_index"); /* fatal */ if (definitions == 0) return -1; /* we should set these individually but each field is properly initialized to zero */ memset(definitions, 0, sizeof(pcl_command_definitions_t)); /* plug command definitions into the parser state and a pointer for the command definitions into pcl's state for use by macros, I don't like this but the alternative is getting the parser state to the code that executer macros which is inconvenient at this time */ pcs->pcl_commands = pcl_parser_state->definitions = definitions; return 0; } /* for now deallocates the memory associated with the command definitions */ int pcl_parser_shutdown(pcl_parser_state_t * pcl_parser_state, gs_memory_t * mem) { gs_free_object(mem, pcl_parser_state->definitions, "pcl_parser_shutdown"); return 0; } /* ---------------- Initialization ---------------- */ int pcparse_do_reset(pcl_state_t * pcs, pcl_reset_type_t type) { if (type & (pcl_reset_initial | pcl_reset_printer)) pcs->parse_other = 0; return 0; } const pcl_init_t pcparse_init = { 0, pcparse_do_reset };