Coverage Report

Created: 2025-06-24 07:01

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