xref: /plan9/sys/src/cmd/gs/src/zdscpars.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 2000 Artifex Software Inc.   All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15  */
16 
17 /* $Id: zdscpars.c,v 1.17 2004/11/17 19:48:01 ray Exp $ */
18 /* C language interface routines to DSC parser */
19 
20 /*
21  * The DSC parser consists of three pieces.  The first piece is a DSC parser
22  * which was coded by Russell Lang (dscparse.c and dscparse.h).  The second
23  * piece is this module.  These two are sufficient to parse DSC comments
24  * and make them available to a client written in PostScript.  The third
25  * piece is a PostScript language module (gs_dscp.ps) that uses certain
26  * comments to affect the interpretation of the file.
27  *
28  * The .initialize_dsc_parser operator defined in this file creates an
29  * instance of Russell's parser, and puts it in a client-supplied dictionary
30  * under a known name (/DSC_struct).
31  *
32  * When the PostScript scanner sees a possible DSC comment (first characters
33  * in a line are %%), it calls the procedure that is the value of the user
34  * parameter ProcessDSCComments.  This procedure should loads the dictionary
35  * that was passed to .initialize_dsc_parser, and then call the
36  * .parse_dsc_comments operator defined in this file.
37  *
38  * These two operators comprise the interface between PostScript and C code.
39  *
40  * There is a "feature" named usedsc that loads a PostScript file
41  * (gs_dscp.ps), which installs a simple framework for processing DSC
42  * comments and having them affect interpretation of the file (e.g., by
43  * setting page device parameters).  See gs_dscp.ps for more information.
44  *
45  * .parse_dsc_comments pulls the comment string off of the stack and passes
46  * it to Russell's parser.  That parser parses the comment and puts any
47  * parameter values into a DSC structure.  That parser also returns a code
48  * which indicates which type of comment was found.  .parse_dsc_comments
49  * looks at the return code and transfers any interesting parameters from
50  * the DSC structure into key value pairs in the dsc_dict dictionary.  It
51  * also translates the comment type code into a key name (comment name).
52  * The key name is placed on the operand stack.  Control then returns to
53  * PostScript code, which can pull the key name from the operand stack and
54  * use it to determine what further processing needs to be done at the PS
55  * language level.
56  *
57  * To add support for new DSC comments:
58  *
59  * 1. Verify that Russell's parser supports the comment.  If not, then add
60  *    the required support.
61  *
62  * 2. Add an entry into DSCcmdlist.  This table contains three values for
63  *    each command that we support.  The first is Russell's return code for
64  *    the command. The second is the key name that we pass back on the
65  *    operand stack.  (Thus this table translates Russell's codes into key
66  *    names for the PostScript client.)  The third entry is a pointer to a
67  *    local function for transferring values from Russell's DSC structure
68  *    into key/value pairs in dsc_dict.
69  *
70  * 3. Create the local function described at the end of the last item.
71  *    There are some support routines like dsc_put_integer() and
72  *    dsc_put_string() to help implement these functions.
73  *
74  * 4. If the usedsc feature should recognize and process the new comments,
75  *    add a processing routine into the dictionary DSCparserprocs in
76  *    gs_dscp.ps.  The keys in this dictionary are the key names described
77  *    in item 2 above.
78  */
79 
80 #include "ghost.h"
81 #include "string_.h"
82 #include "memory_.h"
83 #include "gsstruct.h"
84 #include "ialloc.h"
85 #include "iname.h"
86 #include "istack.h"		/* for iparam.h */
87 #include "iparam.h"
88 #include "ivmspace.h"
89 #include "oper.h"
90 #include "estack.h"
91 #include "store.h"
92 #include "idict.h"
93 #include "iddict.h"
94 #include "dscparse.h"
95 
96 /*
97  * Declare the structure we use to represent an instance of the parser
98  * as a t_struct.  Currently it just saves a pointer to Russell's
99  * data structure.
100  */
101 typedef struct dsc_data_s {
102     CDSC *dsc_data_ptr;
103 } dsc_data_t;
104 
105 /* Structure descriptors */
106 private void dsc_finalize(void *vptr);
107 gs_private_st_simple_final(st_dsc_data_t, dsc_data_t, "dsc_data_struct", dsc_finalize);
108 
109 /* Define the key name for storing the instance pointer in a dictionary. */
110 private const char * const dsc_dict_name = "DSC_struct";
111 
112 /* ---------------- Initialization / finalization ---------------- */
113 
114 /*
115  * If we return CDSC_OK then Russell's parser will make it best guess when
116  * it encounters unexpected comment situations.
117  */
118 private int
dsc_error_handler(void * caller_data,CDSC * dsc,unsigned int explanation,const char * line,unsigned int line_len)119 dsc_error_handler(void *caller_data, CDSC *dsc, unsigned int explanation,
120 		  const char *line, unsigned int line_len)
121 {
122     return CDSC_OK;
123 }
124 
125 /*
126  * This operator creates a new, initialized instance of the DSC parser.
127  */
128 /* <dict> .initialize_dsc_parser - */
129 private int
zinitialize_dsc_parser(i_ctx_t * i_ctx_p)130 zinitialize_dsc_parser(i_ctx_t *i_ctx_p)
131 {
132     ref local_ref;
133     int code;
134     os_ptr const op = osp;
135     dict * const pdict = op->value.pdict;
136     gs_memory_t * const mem = (gs_memory_t *)dict_memory(pdict);
137     dsc_data_t * const data =
138 	gs_alloc_struct(mem, dsc_data_t, &st_dsc_data_t,
139 			"DSC parser init");
140 
141     data->dsc_data_ptr = dsc_init((void *) "Ghostscript DSC parsing");
142     if (!data->dsc_data_ptr)
143     	return_error(e_VMerror);
144     dsc_set_error_function(data->dsc_data_ptr, dsc_error_handler);
145     make_astruct(&local_ref, a_readonly | r_space(op), (byte *) data);
146     code = idict_put_string(op, dsc_dict_name, &local_ref);
147     if (code >= 0)
148 	pop(1);
149     return code;
150 }
151 
152 /*
153  * This routine will free the memory associated with Russell's parser.
154  */
155 private void
dsc_finalize(void * vptr)156 dsc_finalize(void *vptr)
157 {
158     dsc_data_t * const st = vptr;
159 
160     if (st->dsc_data_ptr)
161 	dsc_free(st->dsc_data_ptr);
162     st->dsc_data_ptr = NULL;
163 }
164 
165 
166 /* ---------------- Parsing ---------------- */
167 
168 /* ------ Utilities for returning values ------ */
169 
170 /* Return an integer value. */
171 private int
dsc_put_int(gs_param_list * plist,const char * keyname,int value)172 dsc_put_int(gs_param_list *plist, const char *keyname, int value)
173 {
174     return param_write_int(plist, keyname, &value);
175 }
176 
177 /* Return a string value. */
178 private int
dsc_put_string(gs_param_list * plist,const char * keyname,const char * string)179 dsc_put_string(gs_param_list *plist, const char *keyname,
180 	       const char *string)
181 {
182     gs_param_string str;
183 
184     param_string_from_transient_string(str, string);
185     return param_write_string(plist, keyname, &str);
186 }
187 
188 /* Return a BoundingBox value. */
189 private int
dsc_put_bounding_box(gs_param_list * plist,const char * keyname,const CDSCBBOX * pbbox)190 dsc_put_bounding_box(gs_param_list *plist, const char *keyname,
191 		     const CDSCBBOX *pbbox)
192 {
193     /* pbbox is NULL iff the bounding box values was "(atend)". */
194     int values[4];
195     gs_param_int_array va;
196 
197     if (!pbbox)
198 	return 0;
199     values[0] = pbbox->llx;
200     values[1] = pbbox->lly;
201     values[2] = pbbox->urx;
202     values[3] = pbbox->ury;
203     va.data = values;
204     va.size = 4;
205     va.persistent = false;
206     return param_write_int_array(plist, keyname, &va);
207 }
208 
209 /* ------ Return values for individual comment types ------ */
210 
211 /*
212  * These routines transfer data from the C structure into Postscript
213  * key/value pairs in a dictionary.
214  */
215 private int
dsc_adobe_header(gs_param_list * plist,const CDSC * pData)216 dsc_adobe_header(gs_param_list *plist, const CDSC *pData)
217 {
218     return dsc_put_int(plist, "EPSF", (int)(pData->epsf? 1: 0));
219 }
220 
221 private int
dsc_creator(gs_param_list * plist,const CDSC * pData)222 dsc_creator(gs_param_list *plist, const CDSC *pData)
223 {
224     return dsc_put_string(plist, "Creator", pData->dsc_creator );
225 }
226 
227 private int
dsc_creation_date(gs_param_list * plist,const CDSC * pData)228 dsc_creation_date(gs_param_list *plist, const CDSC *pData)
229 {
230     return dsc_put_string(plist, "CreationDate", pData->dsc_date );
231 }
232 
233 private int
dsc_title(gs_param_list * plist,const CDSC * pData)234 dsc_title(gs_param_list *plist, const CDSC *pData)
235 {
236     return dsc_put_string(plist, "Title", pData->dsc_title );
237 }
238 
239 private int
dsc_for(gs_param_list * plist,const CDSC * pData)240 dsc_for(gs_param_list *plist, const CDSC *pData)
241 {
242     return dsc_put_string(plist, "For", pData->dsc_for);
243 }
244 
245 private int
dsc_bounding_box(gs_param_list * plist,const CDSC * pData)246 dsc_bounding_box(gs_param_list *plist, const CDSC *pData)
247 {
248     return dsc_put_bounding_box(plist, "BoundingBox", pData->bbox);
249 }
250 
251 private int
dsc_page(gs_param_list * plist,const CDSC * pData)252 dsc_page(gs_param_list *plist, const CDSC *pData)
253 {
254     int page_num = pData->page_count;
255 
256     if (page_num)		/* If we have page information */
257         return dsc_put_int(plist, "PageNum",
258 		       pData->page[page_num - 1].ordinal );
259     else			/* No page info - so return page=0 */
260         return dsc_put_int(plist, "PageNum", 0 );
261 }
262 
263 private int
dsc_pages(gs_param_list * plist,const CDSC * pData)264 dsc_pages(gs_param_list *plist, const CDSC *pData)
265 {
266     return dsc_put_int(plist, "NumPages", pData->page_pages);
267 }
268 
269 private int
dsc_page_bounding_box(gs_param_list * plist,const CDSC * pData)270 dsc_page_bounding_box(gs_param_list *plist, const CDSC *pData)
271 {
272     return dsc_put_bounding_box(plist, "PageBoundingBox", pData->page_bbox);
273 }
274 
275 /*
276  * Translate Russell's defintions of orientation into Postscript's.
277  */
278 private int
convert_orient(CDSC_ORIENTATION_ENUM orient)279 convert_orient(CDSC_ORIENTATION_ENUM orient)
280 {
281     switch (orient) {
282     case CDSC_PORTRAIT: return 0;
283     case CDSC_LANDSCAPE: return 1;
284     case CDSC_UPSIDEDOWN: return 2;
285     case CDSC_SEASCAPE: return 3;
286     default: return -1;
287     }
288 }
289 
290 private int
dsc_page_orientation(gs_param_list * plist,const CDSC * pData)291 dsc_page_orientation(gs_param_list *plist, const CDSC *pData)
292 {
293     int page_num = pData->page_count;
294 
295     /*
296      * The PageOrientation comment might be either in the 'defaults'
297      * section or in a page section.  If in the defaults then fhe value
298      * will be in page_orientation.
299      */
300     if (page_num && pData->page[page_num - 1].orientation != CDSC_ORIENT_UNKNOWN)
301 	return dsc_put_int(plist, "PageOrientation",
302 			convert_orient(pData->page[page_num - 1].orientation));
303     else
304         return dsc_put_int(plist, "Orientation",
305 			   convert_orient(pData->page_orientation));
306 }
307 
308 private int
dsc_orientation(gs_param_list * plist,const CDSC * pData)309 dsc_orientation(gs_param_list *plist, const CDSC *pData)
310 {
311     return dsc_put_int(plist, "Orientation",
312 			   convert_orient(pData->page_orientation));
313 }
314 
315 private int
dsc_viewing_orientation(gs_param_list * plist,const CDSC * pData)316 dsc_viewing_orientation(gs_param_list *plist, const CDSC *pData)
317 {
318     int page_num = pData->page_count;
319     const char *key;
320     const CDSCCTM *pctm;
321     float values[4];
322     gs_param_float_array va;
323 
324     /*
325      * As for PageOrientation, ViewingOrientation may be either in the
326      * 'defaults' section or in a page section.
327      */
328     if (page_num && pData->page[page_num - 1].viewing_orientation != NULL) {
329 	key = "PageViewingOrientation";
330 	pctm = pData->page[page_num - 1].viewing_orientation;
331     } else {
332         key = "ViewingOrientation";
333 	pctm = pData->viewing_orientation;
334     }
335     values[0] = pctm->xx;
336     values[1] = pctm->xy;
337     values[2] = pctm->yx;
338     values[3] = pctm->yy;
339     va.data = values;
340     va.size = 4;
341     va.persistent = false;
342     return param_write_float_array(plist, key, &va);
343 }
344 
345 /*
346  * This list is used to translate the commment code returned
347  * from Russell's DSC parser, define a name, and a parameter procedure.
348  */
349 typedef struct cmd_list_s {
350     int code;			/* Russell's DSC parser code (see dsc.h) */
351     const char *comment_name;	/* A name to be returned to postscript caller */
352     int (*dsc_proc) (gs_param_list *, const CDSC *);
353 				/* A routine for transferring parameter values
354 				   from C data structure to postscript dictionary
355 				   key/value pairs. */
356 } cmdlist_t;
357 
358 private const cmdlist_t DSCcmdlist[] = {
359     { CDSC_PSADOBE,	    "Header",		dsc_adobe_header },
360     { CDSC_CREATOR,	    "Creator",		dsc_creator },
361     { CDSC_CREATIONDATE,    "CreationDate",	dsc_creation_date },
362     { CDSC_TITLE,	    "Title",		dsc_title },
363     { CDSC_FOR,		    "For",		dsc_for },
364     { CDSC_BOUNDINGBOX,     "BoundingBox",	dsc_bounding_box },
365     { CDSC_ORIENTATION,	    "Orientation",	dsc_orientation },
366     { CDSC_BEGINDEFAULTS,   "BeginDefaults",	NULL },
367     { CDSC_ENDDEFAULTS,     "EndDefaults",	NULL },
368     { CDSC_PAGE,	    "Page",		dsc_page },
369     { CDSC_PAGES,	    "Pages",		dsc_pages },
370     { CDSC_PAGEORIENTATION, "PageOrientation",  dsc_page_orientation },
371     { CDSC_PAGEBOUNDINGBOX, "PageBoundingBox",	dsc_page_bounding_box },
372     { CDSC_VIEWINGORIENTATION, "ViewingOrientation", dsc_viewing_orientation },
373     { CDSC_EOF,		    "EOF",		NULL },
374     { 0,		    "NOP",		NULL }  /* Table terminator */
375 };
376 
377 /* ------ Parser interface ------ */
378 
379 /*
380  * There are a few comments that we do not want to send to Russell's
381  * DSC parser.  If we send the data block type comments, Russell's
382  * parser will want to skip the specified block of data.  This is not
383  * appropriate for our situation.  So we use this list to check for this
384  * type of comment and do not send it to Russell's parser if found.
385  */
386 private const char * const BadCmdlist[] = {
387     "%%BeginData:",
388     "%%EndData",
389     "%%BeginBinary:",
390     "%%EndBinary",
391     NULL			    /* List terminator */
392 };
393 
394 /* See comments at start of module for description. */
395 /* <dict> <string> .parse_dsc_comments <dict> <dsc code> */
396 private int
zparse_dsc_comments(i_ctx_t * i_ctx_p)397 zparse_dsc_comments(i_ctx_t *i_ctx_p)
398 {
399 #define MAX_DSC_MSG_SIZE (DSC_LINE_LENGTH + 4)	/* Allow for %% and CR/LF */
400     os_ptr const opString = osp;
401     os_ptr const opDict = opString - 1;
402     uint ssize;
403     int comment_code, code;
404     char dsc_buffer[MAX_DSC_MSG_SIZE + 2];
405     const cmdlist_t *pCmdList = DSCcmdlist;
406     const char * const *pBadList = BadCmdlist;
407     ref * pvalue;
408     CDSC * dsc_data = NULL;
409     dict_param_list list;
410 
411     /*
412      * Verify operand types and length of DSC comment string.  If a comment
413      * is too long then we simply truncate it.  Russell's parser gets to
414      * handle any errors that may result.  (Crude handling but the comment
415      * is bad, so ...).
416      */
417     check_type(*opString, t_string);
418     check_dict_write(*opDict);
419     ssize = r_size(opString);
420     if (ssize > MAX_DSC_MSG_SIZE)   /* need room for EOL + \0 */
421         ssize = MAX_DSC_MSG_SIZE;
422     /*
423      * Pick up the comment string to be parsed.
424      */
425     memcpy(dsc_buffer, opString->value.bytes, ssize);
426     dsc_buffer[ssize] = 0x0d;	    /* Russell wants a 'line end' */
427     dsc_buffer[ssize + 1] = 0;	    /* Terminate string */
428     /*
429      * Skip data block comments (see comments in front of BadCmdList).
430      */
431     while (*pBadList && strncmp(*pBadList, dsc_buffer, strlen(*pBadList)))
432         pBadList++;
433     if (*pBadList) {		    /* If found in list, then skip comment */
434         comment_code = 0;	    /* Force NOP */
435     }
436     else {
437         /*
438          * Parse comments - use Russell Lang's DSC parser.  We need to get
439          * data area for Russell Lang's parser.  Note: We have saved the
440          * location of the data area for the parser in our DSC dict.
441          */
442         code = dict_find_string(opDict, dsc_dict_name, &pvalue);
443 	dsc_data = r_ptr(pvalue, dsc_data_t)->dsc_data_ptr;
444         if (code < 0)
445             return code;
446         comment_code = dsc_scan_data(dsc_data, dsc_buffer, ssize + 1);
447         if_debug1('%', "[%%].parse_dsc_comments: code = %d\n", comment_code);
448 	/*
449 	 * We ignore any errors from Russell's parser.  The only value that
450 	 * it will return for an error is -1 so there is very little information.
451 	 * We also do not want bad DSC comments to abort processing of an
452 	 * otherwise valid PS file.
453 	 */
454         if (comment_code < 0)
455 	    comment_code = 0;
456     }
457     /*
458      * Transfer data from DSC structure to postscript variables.
459      * Look up proper handler in the local cmd decode list.
460      */
461     while (pCmdList->code && pCmdList->code != comment_code )
462 	pCmdList++;
463     if (pCmdList->dsc_proc) {
464 	code = dict_param_list_write(&list, opDict, NULL, iimemory);
465 	if (code < 0)
466 	    return code;
467 	code = (pCmdList->dsc_proc)((gs_param_list *)&list, dsc_data);
468 	iparam_list_release(&list);
469 	if (code < 0)
470 	    return code;
471     }
472 
473     /* Put DSC comment name onto operand stack (replace string). */
474 
475     return name_enter_string(imemory, pCmdList->comment_name, opString);
476 }
477 
478 /* ------ Initialization procedure ------ */
479 
480 const op_def zdscpars_op_defs[] = {
481     {"1.initialize_dsc_parser", zinitialize_dsc_parser},
482     {"2.parse_dsc_comments", zparse_dsc_comments},
483     op_def_end(0)
484 };
485