Coverage Report

Created: 2026-10-28 08:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/pcl/pxl/pxgstate.c
Line
Count
Source
1
/* Copyright (C) 2001-2024 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
/* pxgstate.c */
18
/* PCL XL graphics state operators */
19
20
#include "math_.h"
21
#include "memory_.h"
22
#include "stdio_.h"             /* std.h + NULL */
23
#include "gstypes.h"
24
#include "gsmemory.h"
25
#include "gsstruct.h"
26
#include "pxoper.h"
27
#include "pxstate.h"
28
#include "pxparse.h"
29
#include "gdebug.h"
30
#include "gsstate.h"
31
#include "gscoord.h"
32
#include "gxcspace.h"           /* must precede gscolor2.h */
33
#include "gscie.h"
34
#include "gsicc.h"
35
#include "gsicc_manage.h"
36
#include "gsimage.h"
37
#include "gspath.h"
38
#include "gspath2.h"
39
#include "gsrop.h"
40
#include "gxpath.h"
41
#include "gzstate.h"
42
#include "gscolor2.h"
43
#include "pxptable.h"
44
45
/*
46
 * There is an apparent bug in the LJ5 and LJ6MP firmware that causes
47
 * SetClipIntersect with even/odd mode and exterior region to behave the
48
 * same as SetClipReplace.  To emulate this bug, uncomment the following
49
 * #define.
50
 */
51
#define CLIP_INTERSECT_EXTERIOR_REPLACES
52
/*
53
 * H-P printers apparently have a maximum dash pattern length of 20.
54
 * Our library doesn't impose a limit, but we may as well emulate the
55
 * H-P limit here for error checking and Genoa test compatibility.
56
 */
57
0
#define MAX_DASH_ELEMENTS 20
58
59
/* Imported operators */
60
px_operator_proc(pxRectanglePath);
61
/* Forward references */
62
px_operator_proc(pxSetClipIntersect);
63
64
/* Imported color space types */
65
extern const gs_color_space_type gs_color_space_type_Indexed;   /* gscolor2.c */
66
67
extern const gs_color_space_type gs_color_space_type_Pattern;   /* gspcolor.c */
68
69
/* Define 'client procedures' for copying the PCL XL state. */
70
/* We export the reference count adjustment procedures for the sake of */
71
/* the state setting code in pxink.c. */
72
void
73
px_paint_rc_adjust(px_paint_t * ppt, int delta, gs_memory_t * mem)
74
5.45M
{
75
5.45M
    if (ppt->type == pxpPattern) {
76
        /*
77
         * There is no public API for adjusting the reference count of a
78
         * gs_client_color, and even the private API requires having a
79
         * color space available.  We'll need to fix this properly
80
         * sooner or later, but for the moment, fake it.
81
         */
82
81.7k
        gs_color_space cspace;
83
84
        /*
85
         * Even though this is a colored Pattern, and hence does have a
86
         * base space, we set has_base_space to false to prevent the
87
         * adjust_color_count procedure from trying to call the
88
         * adjustment procedures for the base space.
89
         */
90
81.7k
        cspace.type = &gs_color_space_type_Pattern;
91
81.7k
        cspace.params.pattern.has_base_space = false;
92
81.7k
        (*cspace.type->adjust_color_count) (&ppt->value.pattern.color,
93
81.7k
                                            &cspace, delta);
94
81.7k
        rc_adjust_only(ppt->value.pattern.pattern, delta,
95
81.7k
                       "px_paint_rc_adjust");
96
81.7k
    }
97
5.45M
}
98
void
99
px_gstate_rc_adjust(px_gstate_t * pxgs, int delta, gs_memory_t * mem)
100
2.72M
{
101
2.72M
    px_paint_rc_adjust(&pxgs->pen, delta, mem);
102
2.72M
    px_paint_rc_adjust(&pxgs->brush, delta, mem);
103
2.72M
}
104
static void *
105
px_gstate_client_alloc(gs_memory_t * mem)
106
557k
{
107
557k
    px_gstate_t *pxgs = (px_gstate_t *) gs_alloc_bytes(mem,
108
557k
                                                       sizeof(px_gstate_t),
109
557k
                                                       "px_gstate_alloc");
110
111
557k
    if (pxgs == 0)
112
0
        return 0;
113
    /* Initialize reference-counted pointers and other pointers */
114
    /* needed to establish invariants. */
115
557k
    pxgs->memory = mem;
116
557k
    pxgs->halftone.thresholds.data = 0;
117
557k
    pxgs->halftone.thresholds.size = 0;
118
557k
    pxgs->dither_matrix.data = 0;
119
557k
    pxgs->dither_matrix.size = 0;
120
557k
    pxgs->brush.type = pxpNull;
121
557k
    pxgs->pen.type = pxpNull;
122
557k
    px_dict_init(&pxgs->temp_pattern_dict, mem, px_free_pattern);
123
557k
    return pxgs;
124
557k
}
125
126
static int
127
px_gstate_client_copy_for(void *to, void *from, gs_gstate_copy_reason_t reason)
128
1.08M
{
129
6.49M
#define pxfrom ((px_gstate_t *)from)
130
6.49M
#define pxto ((px_gstate_t *)to)
131
1.08M
    px_gstate_rc_adjust(pxfrom, 1, pxfrom->memory);
132
1.08M
    px_gstate_rc_adjust(pxto, -1, pxto->memory);
133
    /*
134
     * In the context of the PCL XL code, this routine may be called for
135
     * gsave, grestore, or gstate (copying the gstate for makepattern
136
     * or Pattern rendering).  See gxstate.h for details of the 'from'
137
     * and 'to' arguments for each of these cases.
138
     *
139
     * We have some structures that belong to the individual gstates for
140
     * storage management purposes.  Currently these are:
141
     *      dither_matrix, temp_pattern_dict
142
     * px_gstate_client_alloc initializes them to an empty state.
143
     * For gsave and gstate, the new current gstate or copied gstate
144
     * respectively should have the empty structure.  For grestore,
145
     * we want to swap the structures, because we will then free the
146
     * saved gstate (and release the structure that was in the current
147
     * gstate before the grestore).
148
     *
149
     * halftone.thresholds is a different special case.  We need to
150
     * copy it for gsave and gstate, and free it on grestore.
151
     */
152
1.08M
    {
153
1.08M
        gs_string tmat;
154
1.08M
        gs_string thtt;
155
1.08M
        pl_dict_t tdict;
156
1.08M
        gs_string *phtt;
157
158
1.08M
        tmat = pxto->dither_matrix;
159
1.08M
        thtt = pxto->halftone.thresholds;
160
1.08M
        tdict = pxto->temp_pattern_dict;
161
1.08M
        *pxto = *pxfrom;
162
1.08M
        switch (reason) {
163
231
            case copy_for_gstate:
164
                /* Just put back the (empty) 'to' structures. */
165
231
                pxto->dither_matrix = tmat;
166
231
                pxto->temp_pattern_dict = tdict;
167
231
                phtt = &pxto->halftone.thresholds;
168
231
                goto copy;
169
540k
            case copy_for_gsave:
170
                /* Swap the structures, but set the parent of the new */
171
                /* (empty) dictionary to the old one. */
172
540k
                pxfrom->dither_matrix = tmat;
173
540k
                pxfrom->temp_pattern_dict = tdict;
174
540k
                pl_dict_set_parent(&pxfrom->temp_pattern_dict,
175
540k
                                   &pxto->temp_pattern_dict);
176
540k
                phtt = &pxfrom->halftone.thresholds;
177
541k
              copy:if (phtt->data) {
178
0
                    byte *str = gs_alloc_string(pxfrom->memory, phtt->size,
179
0
                                                "px_gstate_client_copy(thresholds)");
180
181
0
                    if (str == 0)
182
0
                        return_error(errorInsufficientMemory);
183
0
                    memcpy(str, phtt->data, phtt->size);
184
0
                    phtt->data = str;
185
0
                }
186
541k
                break;
187
541k
            default:           /* copy_for_grestore */
188
                /* Swap the structures. */
189
540k
                pxfrom->dither_matrix = tmat;
190
540k
                pxfrom->halftone.thresholds = thtt;
191
540k
                pxfrom->temp_pattern_dict = tdict;
192
1.08M
        }
193
1.08M
    }
194
1.08M
    return 0;
195
1.08M
#undef pxfrom
196
1.08M
#undef pxto
197
1.08M
}
198
static void
199
px_gstate_client_free(void *old, gs_memory_t * mem, gs_gstate *pgs)
200
557k
{
201
557k
    px_gstate_t *pxgs = old;
202
203
557k
    px_dict_release(&pxgs->temp_pattern_dict);
204
557k
    if (pxgs->halftone.thresholds.data)
205
0
        gs_free_string(mem, (byte *) pxgs->halftone.thresholds.data,
206
557k
                       pxgs->halftone.thresholds.size,
207
557k
                       "px_gstate_free(halftone.thresholds)");
208
557k
    if (pxgs->dither_matrix.data)
209
0
        gs_free_string(mem, (byte *) pxgs->dither_matrix.data,
210
557k
                       pxgs->dither_matrix.size,
211
557k
                       "px_gstate_free(dither_matrix)");
212
557k
    px_gstate_rc_adjust(old, -1, mem);
213
557k
    gs_free_object(mem, old, "px_gstate_free");
214
557k
}
215
216
static const gs_gstate_client_procs px_gstate_procs = {
217
    px_gstate_client_alloc,
218
    0,                          /* copy -- superseded by copy_for */
219
    px_gstate_client_free,
220
    px_gstate_client_copy_for
221
};
222
223
/* ---------------- Initialization ---------------- */
224
225
/* Allocate a px_gstate_t. */
226
px_gstate_t *
227
px_gstate_alloc(gs_memory_t * mem)
228
16.0k
{
229
16.0k
    px_gstate_t *pxgs = px_gstate_client_alloc(mem);
230
231
16.0k
    if (pxgs == 0)
232
0
        return 0;
233
16.0k
    pxgs->stack_depth = 0;
234
16.0k
    px_gstate_init(pxgs, NULL);
235
16.0k
    return pxgs;
236
16.0k
}
237
238
/* Initialize a px_gstate_t. */
239
void
240
px_gstate_init(px_gstate_t * pxgs, gs_gstate * pgs)
241
48.0k
{
242
48.0k
    pxgs->halftone.method = eDeviceBest;
243
48.0k
    pxgs->halftone.set = false;
244
48.0k
    pxgs->halftone.origin.x = pxgs->halftone.origin.y = 0;
245
    /* halftone.thresholds was initialized at alloc time */
246
48.0k
    px_gstate_reset(pxgs);
247
48.0k
    if (pgs)
248
16.0k
        gs_gstate_set_client(pgs, pxgs, &px_gstate_procs, true);
249
48.0k
}
250
251
/* Initialize the graphics state for a page. */
252
/* Note that this takes a px_state_t, not a px_gstate_t. */
253
int
254
px_initgraphics(px_state_t * pxs)
255
9.12k
{
256
9.12k
    gs_gstate *pgs = pxs->pgs;
257
9.12k
    int code;
258
259
9.12k
    px_gstate_reset(pxs->pxgs);
260
9.12k
    code = gs_initgraphics(pgs);
261
9.12k
    if (code < 0) return code;
262
263
9.12k
    gs_setfilladjust(pgs, 0.5, 0.5);
264
265
9.12k
    {
266
9.12k
        gs_point inch;
267
9.12k
        float dpi;
268
269
9.12k
        gs_dtransform(pgs, 72.0, 0.0, &inch);
270
9.12k
        dpi = fabs(inch.x) + fabs(inch.y);
271
272
        /* Stroke adjustment leads to anomalies at high resolutions. */
273
9.12k
        if (dpi >= 150)
274
0
            gs_setstrokeadjust(pgs, false);
275
276
        /* We need the H-P interpretation of zero-length lines */
277
        /* and of using bevel joins for the segments of flattened curves. */
278
9.12k
        code = gs_setdotlength(pgs, 72.0 / 300, true);
279
9.12k
        if (code < 0) return code;
280
9.12k
    }
281
    /* we always clamp coordinates hp does not seem to report
282
       limit checks in paths */
283
9.12k
    gs_setlimitclamp(pgs, true);
284
9.12k
    return 0;
285
9.12k
}
286
287
/* Reset a px_gstate_t, initially or at the beginning of a page. */
288
void
289
px_gstate_reset(px_gstate_t * pxgs)
290
57.1k
{
291
57.1k
    pxgs->brush.type = pxpGray;
292
57.1k
    pxgs->brush.value.gray = 0;
293
57.1k
    pxgs->char_angle = 0;
294
57.1k
    pxgs->char_bold_value = 0;
295
57.1k
    pxgs->char_scale.x = pxgs->char_scale.y = 1;
296
57.1k
    pxgs->char_shear.x = pxgs->char_shear.y = 0;
297
    /* The order of transforms is arbitrary, */
298
    /* because the transforms are all no-ops. */
299
57.1k
    pxgs->char_transforms[0] = pxct_rotate;
300
57.1k
    pxgs->char_transforms[1] = pxct_shear;
301
57.1k
    pxgs->char_transforms[2] = pxct_scale;
302
57.1k
    pxgs->char_sub_mode = eNoSubstitution;
303
57.1k
    pxgs->clip_mode = eNonZeroWinding;
304
57.1k
    pxgs->color_space = eRGB;
305
57.1k
    pxgs->palette.size = 0;
306
57.1k
    pxgs->palette.data = 0;
307
57.1k
    pxgs->palette_is_shared = false;
308
57.1k
    pxgs->base_font = 0;
309
    /* halftone_method was set above. */
310
57.1k
    pxgs->fill_mode = eNonZeroWinding;
311
57.1k
    pxgs->dashed = false;
312
57.1k
    pxgs->pen.type = pxpGray;
313
57.1k
    pxgs->pen.value.gray = 0;
314
    /* temp_pattern_dict was set at allocation time. */
315
57.1k
    gs_make_identity(&pxgs->text_ctm);
316
57.1k
    pxgs->char_matrix_set = false;
317
57.1k
    pxgs->symbol_map = 0;
318
57.1k
    pxgs->writing_mode = eHorizontal;
319
57.1k
}
320
321
/* initial clip region note, we don't use gs_initclip() because we
322
   give special handling for the XL 1/6" border */
323
int
324
px_initclip(px_state_t * pxs)
325
2.95k
{
326
2.95k
    return gs_initclip(pxs->pgs);
327
2.95k
}
328
329
static bool
330
px_is_currentcolor_pattern(const gs_gstate * pgs)
331
0
{
332
0
    return (gs_color_space_num_components(gs_currentcolorspace(pgs)) < 1);
333
0
}
334
335
/* Set up the color space information for a bitmap image or pattern. */
336
int
337
px_image_color_space(gs_image_t * pim,
338
                     const px_bitmap_params_t * params,
339
                     const gs_string * palette, const gs_gstate * pgs)
340
0
{
341
342
0
    int depth = params->depth;
343
0
    gs_color_space *pbase_pcs = NULL;
344
0
    gs_color_space *pcs = NULL;
345
0
    bool cie_space = false;
346
0
    int code = 0;
347
348
0
    switch (params->color_space) {
349
0
        case eGray:
350
0
            pbase_pcs = gs_cspace_new_DeviceGray(pgs->memory);
351
0
            if (pbase_pcs == NULL)
352
0
                return_error(errorInsufficientMemory);
353
0
            pbase_pcs->cmm_icc_profile_data = pgs->icc_manager->default_gray;
354
0
            pbase_pcs->type = &gs_color_space_type_ICC;
355
0
            rc_increment(pbase_pcs->cmm_icc_profile_data);
356
0
            break;
357
0
        case eRGB:
358
0
            pbase_pcs = gs_cspace_new_DeviceRGB(pgs->memory);
359
0
            if (pbase_pcs == NULL)
360
0
                return_error(errorInsufficientMemory);
361
0
            pbase_pcs->cmm_icc_profile_data = pgs->icc_manager->default_rgb;
362
0
            pbase_pcs->type = &gs_color_space_type_ICC;
363
0
            rc_increment(pbase_pcs->cmm_icc_profile_data);
364
0
            break;
365
0
        case eSRGB:
366
0
            cie_space = true;
367
0
            pbase_pcs = gs_cspace_new_DeviceRGB(pgs->memory);
368
0
            if (pbase_pcs == NULL)
369
0
                return_error(errorInsufficientMemory);
370
0
            pbase_pcs->cmm_icc_profile_data = pgs->icc_manager->default_rgb;
371
0
            pbase_pcs->type = &gs_color_space_type_ICC;
372
0
            rc_increment(pbase_pcs->cmm_icc_profile_data);
373
0
            break;
374
0
        default:
375
0
            return_error(errorIllegalAttributeValue);
376
0
    }
377
378
0
    if (params->indexed) {
379
0
        pcs = gs_cspace_alloc(pgs->memory, &gs_color_space_type_Indexed);
380
0
        if (pcs == NULL) {
381
            /* free the base space also */
382
0
            rc_decrement(pbase_pcs, "px_image_color_space");
383
0
            return_error(errorInsufficientMemory);
384
0
        }
385
0
        pcs->base_space = pbase_pcs;
386
0
        pcs->params.indexed.hival = (1 << depth) - 1;
387
0
        pcs->params.indexed.lookup.table.size = palette->size;
388
0
        {
389
0
            uint n = palette->size;
390
0
            byte *p = gs_alloc_string(pgs->memory, n,
391
0
                                      "px_image_color_space(palette)");
392
393
0
            if (p == 0) {
394
0
                rc_decrement(pbase_pcs, "px_image_color_space");
395
0
                return_error(errorInsufficientMemory);
396
397
0
            }
398
0
            memcpy(p, palette->data, n);
399
0
            pcs->params.indexed.lookup.table.data = p;
400
0
        }
401
0
        pcs->params.indexed.use_proc = 0;
402
0
    } else {
403
0
        pcs = pbase_pcs;
404
0
    }
405
0
    gs_image_t_init(pim, pcs);
406
0
    pim->ColorSpace = pcs;
407
0
    pim->BitsPerComponent = depth;
408
0
    if (params->indexed)
409
0
        pim->Decode[1] = (float)((1 << depth) - 1);
410
    /* NB - this needs investigation */
411
0
    if (cie_space && !px_is_currentcolor_pattern(pgs)) {
412
0
        code = gs_setrgbcolor((gs_gstate *) pgs, 0.0, 0.0, 0.0);
413
0
    }
414
0
    return code;
415
0
}
416
417
/* Check the setting of the clipping region. */
418
#define check_clip_region(par, pxs)\
419
6.27k
  if ( par->pv[0]->value.i == eExterior && pxs->pxgs->clip_mode != eEvenOdd )\
420
6.27k
    return_error(errorClipModeMismatch)
421
422
/* Record the most recent character transformation. */
423
static void
424
add_char_transform(px_gstate_t * pxgs, px_char_transform_t trans)
425
46
{                               /* Promote this transformation to the head of the list. */
426
46
    if (pxgs->char_transforms[2] == trans)
427
14
        pxgs->char_transforms[2] = pxgs->char_transforms[1],
428
14
            pxgs->char_transforms[1] = pxgs->char_transforms[0];
429
32
    else if (pxgs->char_transforms[1] == trans)
430
0
        pxgs->char_transforms[1] = pxgs->char_transforms[0];
431
46
    pxgs->char_transforms[0] = trans;
432
46
    pxgs->char_matrix_set = false;
433
46
}
434
435
/* ---------------- Operators ---------------- */
436
437
const byte apxPopGS[] = { 0, 0 };
438
int
439
pxPopGS(px_args_t * par, px_state_t * pxs)
440
10.9k
{
441
10.9k
    gs_gstate *pgs = pxs->pgs;
442
10.9k
    px_gstate_t *pxgs = pxs->pxgs;
443
10.9k
    int code;
444
445
    /*
446
     * Even though the H-P documentation says that a PopGS with an
447
     * empty stack is illegal, the implementations apparently simply
448
     * do nothing in this case.
449
     */
450
10.9k
    if (pxgs->stack_depth == 0)
451
1
        return 0;
452
10.9k
    if (pxgs->palette.data && !pxgs->palette_is_shared) {
453
0
        gs_free_string(pxs->memory, (byte *) pxgs->palette.data,
454
0
                       pxgs->palette.size, "pxPopGS(palette)");
455
0
        pxgs->palette.data = 0;
456
0
    }
457
10.9k
    px_purge_pattern_cache(pxs, eTempPattern);
458
10.9k
    code = gs_grestore(pgs);
459
10.9k
    pxs->pxgs = gs_gstate_client_data(pgs);
460
10.9k
    return code;
461
10.9k
}
462
463
const byte apxPushGS[] = { 0, 0 };
464
int
465
pxPushGS(px_args_t * par, px_state_t * pxs)
466
2.63k
{
467
2.63k
    gs_gstate *pgs = pxs->pgs;
468
2.63k
    int code = gs_gsave(pgs);
469
2.63k
    px_gstate_t *pxgs;
470
471
2.63k
    if (code < 0)
472
0
        return code;
473
2.63k
    pxgs = pxs->pxgs = gs_gstate_client_data(pgs);
474
2.63k
    if (pxgs->palette.data)
475
0
        pxgs->palette_is_shared = true;
476
2.63k
    ++(pxgs->stack_depth);
477
2.63k
    return code;
478
2.63k
}
479
480
/* To restore the interpreters to default state we assemble the
481
   following pxl commands and execute them as a stream.
482
483
ubyte 2 ColorSpace
484
SetColorSpace
485
486
ubyte_array [ uint16 3
487
0000   00 00 00                                           ...
488
] RGBColor
489
SetBrushSource
490
491
uint16 0 CharAngle
492
SetCharAngle
493
494
SetPageDefaultCTM
495
ubyte_array [ uint16 3
496
0000   00 00 00                                           ...
497
498
] RGBColor
499
SetPenSource
500
501
ubyte 1 PenWidth
502
SetPenWidth
503
504
ubyte 0 TxMode
505
SetSourceTxMode
506
507
ubyte 0 TxMode
508
SetPatternTxMode
509
510
ubyte 252 ROP3
511
SetROP
512
513
ubyte 0 LineCapStyle
514
SetLineCap
515
516
ubyte 0 SolidLine
517
SetLineDash
518
519
ubyte 0 LineJoinStyle
520
SetLineJoin
521
522
uint16 10 MiterLength
523
SetMiterLimit
524
525
real32_xy 1.000000 1.000000 CharScale
526
SetCharScale
527
528
real32_xy 0.000000 0.000000 CharShear
529
SetCharShear
530
531
ubyte 0 DeviceMatrix
532
SetHalftoneMethod
533
534
ubyte 0 ClipMode
535
SetClipMode
536
537
SetClipToPage
538
539
NewPath
540
541
ubyte 0 FillMode
542
SetFillMode
543
544
*/
545
546
/* assembled (little endian) stream */
547
static const byte pxSetDefaultGSstr[] = {
548
    192, 2, 248, 3, 106, 200, 193, 3, 0, 0, 0, 0, 248, 11, 99, 193,
549
    0, 0, 248, 161, 100, 116, 200, 193, 3, 0, 0, 0, 0, 248, 11, 121, 192, 1,
550
    248, 75, 122, 192,
551
    0, 248, 45, 124, 192, 0, 248, 45, 120, 192, 252, 248, 44, 123, 192, 0,
552
    248, 71, 113, 192,
553
    0, 248, 78, 112, 192, 0, 248, 72, 114, 193, 10, 0, 248, 73, 115, 213, 0,
554
    0, 128, 63, 0, 0, 128, 63,
555
    248, 164, 101, 213, 0, 0, 0, 0, 0, 0, 0, 0, 248, 165, 102, 192, 0, 248,
556
    33, 109, 192, 0, 248,
557
    84, 127, 105, 133, 192, 0, 248, 70, 110
558
};
559
560
const byte apxSetDefaultGS[] = { 0, 0 };
561
int
562
pxSetDefaultGS(px_args_t * par, px_state_t * pxs)
563
7
{
564
7
    px_parser_state_t *pst = par->parser;
565
7
    px_parser_state_t st;
566
7
    stream_cursor_read r;
567
7
    int code;
568
569
7
    st.memory = pxs->memory;
570
7
    px_process_init(&st, false /* big_endian */ );
571
572
7
    st.macro_state = pst->macro_state | ptsExecStream;
573
574
7
    stream_cursor_read_init(&r, pxSetDefaultGSstr, sizeof(pxSetDefaultGSstr));
575
    /* we don't expect this to fail */
576
7
    code = px_process(&st, pxs, &r);
577
578
7
    pst->macro_state = st.macro_state & ~ptsExecStream;
579
7
    return code;
580
7
}
581
582
const byte apxSetClipReplace[] = {
583
    pxaClipRegion, 0, 0
584
};
585
int
586
pxSetClipReplace(px_args_t * par, px_state_t * pxs)
587
2.45k
{
588
2.45k
    int code;
589
590
2.45k
    check_clip_region(par, pxs);
591
2.45k
    if ((code = px_initclip(pxs)) < 0)
592
0
        return code;
593
2.45k
    return pxSetClipIntersect(par, pxs);
594
2.45k
}
595
596
const byte apxSetCharAngle[] = {
597
    pxaCharAngle, 0, 0
598
};
599
int
600
pxSetCharAngle(px_args_t * par, px_state_t * pxs)
601
3.86k
{
602
3.86k
    real angle = real_value(par->pv[0], 0);
603
3.86k
    px_gstate_t *pxgs = pxs->pxgs;
604
605
3.86k
    if (angle != pxgs->char_angle || pxgs->char_transforms[0] != pxct_rotate) {
606
32
        pxgs->char_angle = angle;
607
32
        add_char_transform(pxgs, pxct_rotate);
608
32
    }
609
3.86k
    return 0;
610
3.86k
}
611
612
/* confusion in the 3.0 spec - this argument identifier WritingMode
613
   173 is now being used by SelectPCLFont */
614
const byte apxSetCharAttributes[] = {
615
    pxaWritingMode, 0, 0
616
};
617
int
618
pxSetCharAttributes(px_args_t * par, px_state_t * pxs)
619
0
{
620
0
    pxeWritingMode_t arg = par->pv[0]->value.i;
621
0
    pxs->pxgs->writing_mode = arg;
622
0
    return 0;
623
0
}
624
625
const byte apxSetCharScale[] = {
626
    pxaCharScale, 0, 0
627
};
628
int
629
pxSetCharScale(px_args_t * par, px_state_t * pxs)
630
7
{
631
7
    real x_scale = real_value(par->pv[0], 0);
632
7
    real y_scale = real_value(par->pv[0], 1);
633
7
    px_gstate_t *pxgs = pxs->pxgs;
634
635
7
    if (x_scale != pxgs->char_scale.x || y_scale != pxgs->char_scale.y ||
636
7
        pxgs->char_transforms[0] != pxct_scale) {
637
7
        pxgs->char_scale.x = x_scale;
638
7
        pxgs->char_scale.y = y_scale;
639
7
        add_char_transform(pxgs, pxct_scale);
640
7
    }
641
7
    return 0;
642
7
}
643
644
const byte apxSetCharShear[] = {
645
    pxaCharShear, 0, 0
646
};
647
648
 /* experiments indicate the character shearing operands are
649
    clamped after range checking though it is not documented in
650
    the HP manual. */
651
652
14
#define SHEAR_LIMIT 16383.0
653
654
int
655
pxSetCharShear(px_args_t * par, px_state_t * pxs)
656
7
{
657
7
    real x_shear = real_value(par->pv[0], 0);
658
7
    real y_shear = real_value(par->pv[0], 1);
659
7
    px_gstate_t *pxgs = pxs->pxgs;
660
661
7
    x_shear = x_shear > SHEAR_LIMIT ? SHEAR_LIMIT : x_shear;
662
7
    y_shear = y_shear > SHEAR_LIMIT ? SHEAR_LIMIT : y_shear;
663
664
7
    if (x_shear != pxgs->char_shear.x || y_shear != pxgs->char_shear.y ||
665
7
        pxgs->char_transforms[0] != pxct_shear) {
666
7
        pxgs->char_shear.x = x_shear;
667
7
        pxgs->char_shear.y = y_shear;
668
7
        add_char_transform(pxgs, pxct_shear);
669
7
    }
670
7
    return 0;
671
7
}
672
673
const byte apxSetClipIntersect[] = {
674
    pxaClipRegion, 0, 0
675
};
676
int
677
pxSetClipIntersect(px_args_t * par, px_state_t * pxs)
678
2.45k
{
679
2.45k
    gs_gstate *pgs = pxs->pgs;
680
2.45k
    pxeClipRegion_t clip_region = par->pv[0]->value.i;
681
2.45k
    int code;
682
683
2.45k
    check_clip_region(par, pxs);
684
685
2.45k
    if (clip_region == eExterior) {
686
        /*
687
         * We know clip_mode is eEvenOdd, so we can complement the
688
         * region defined by the current path by just adding a
689
         * rectangle that encloses the entire page.
690
         */
691
0
        gs_rect bbox;
692
693
0
        code = gs_gsave(pgs);
694
0
        if (code < 0)
695
0
            return code;
696
0
        px_initclip(pxs);
697
0
        if ((code = gs_clippath(pgs)) < 0 ||
698
0
            (code = gs_pathbbox(pgs, &bbox)) < 0)
699
0
            DO_NOTHING;
700
0
        gs_grestore(pgs);
701
0
        if (code < 0 || (code = gs_rectappend(pgs, &bbox, 1)) < 0)
702
0
            return code;
703
0
#ifdef CLIP_INTERSECT_EXTERIOR_REPLACES
704
0
        px_initclip(pxs);
705
0
#endif
706
0
    }
707
2.45k
    code = (pxs->pxgs->clip_mode == eEvenOdd ? gs_eoclip(pgs) : gs_clip(pgs));
708
2.45k
    if (code < 0)
709
0
        return code;
710
711
2.45k
    return gs_newpath(pgs);
712
2.45k
}
713
714
const byte apxSetClipRectangle[] = {
715
    pxaClipRegion, pxaBoundingBox, 0, 0
716
};
717
int
718
pxSetClipRectangle(px_args_t * par, px_state_t * pxs)
719
1.35k
{
720
1.35k
    px_args_t args;
721
1.35k
    gs_gstate *pgs = pxs->pgs;
722
1.35k
    int code;
723
724
1.35k
    check_clip_region(par, pxs);
725
1.35k
    gs_newpath(pgs);
726
1.35k
    args.pv[0] = par->pv[1];
727
1.35k
    if ((code = pxRectanglePath(&args, pxs)) < 0)
728
0
        return code;
729
1.35k
    return pxSetClipReplace(par, pxs);
730
1.35k
}
731
732
const byte apxSetClipToPage[] = { 0, 0 };
733
int
734
pxSetClipToPage(px_args_t * par, px_state_t * pxs)
735
273
{
736
273
    gs_newpath(pxs->pgs);
737
273
    return px_initclip(pxs);
738
273
}
739
740
const byte apxSetCursor[] = {
741
    pxaPoint, 0, 0
742
};
743
744
int
745
pxSetCursor(px_args_t * par, px_state_t * pxs)
746
14.4k
{
747
14.4k
    return gs_moveto(pxs->pgs, real_value(par->pv[0], 0),
748
14.4k
                     real_value(par->pv[0], 1));
749
14.4k
}
750
751
const byte apxSetCursorRel[] = {
752
    pxaPoint, 0, 0
753
};
754
755
int
756
pxSetCursorRel(px_args_t * par, px_state_t * pxs)
757
1
{
758
1
    return gs_rmoveto(pxs->pgs, real_value(par->pv[0], 0),
759
1
                      real_value(par->pv[0], 1));;
760
0
}
761
762
/* SetHalftoneMethod is in pxink.c */
763
const byte apxSetFillMode[] = {
764
    pxaFillMode, 0, 0
765
};
766
767
int
768
pxSetFillMode(px_args_t * par, px_state_t * pxs)
769
447
{
770
447
    pxs->pxgs->fill_mode = par->pv[0]->value.i;
771
447
    return 0;
772
447
}
773
774
/* SetFont is in pxfont.c */
775
const byte apxSetLineDash[] = {
776
    0, pxaLineDashStyle, pxaDashOffset, pxaSolidLine, 0
777
};
778
779
int
780
pxSetLineDash(px_args_t * par, px_state_t * pxs)
781
10
{
782
10
    px_gstate_t *pxgs = pxs->pxgs;
783
10
    gs_gstate *pgs = pxs->pgs;
784
785
10
    if (par->pv[0]) {
786
0
        float pattern[MAX_DASH_ELEMENTS * 2];
787
0
        uint size = par->pv[0]->value.array.size;
788
0
        real offset = (par->pv[1] ? real_value(par->pv[1], 0) : 0.0);
789
0
        int code;
790
791
0
        if (par->pv[2])
792
0
            return_error(errorIllegalAttributeCombination);
793
0
        if (size > MAX_DASH_ELEMENTS || size == 0)
794
0
            return_error(errorIllegalArraySize);
795
796
        /*
797
         * The H-P documentation gives no clue about what a negative
798
         * dash pattern element is supposed to do.  The H-P printers
799
         * apparently interpret it as drawing a line backwards in the
800
         * current direction (which may extend outside the original
801
         * subpath) with the caps inside the line instead of outside; a
802
         * dash pattern with a negative total length crashes the LJ 6MP
803
         * firmware so badly the printer has to be power cycled!  We
804
         * take a different approach here: we compensate for negative
805
         * elements by propagating them to adjacent positive ones.  This
806
         * doesn't produce quite the same output as the H-P printers do,
807
         * but this is such an obscure feature that we don't think it's
808
         * worth the trouble to emulate exactly.
809
         */
810
0
        {
811
0
            uint orig_size = size;
812
0
            int i;
813
814
            /* Acquire the pattern, duplicating it if the length is odd. */
815
0
            if (size & 1)
816
0
                size <<= 1;
817
0
            for (i = 0; i < size; ++i)
818
0
                pattern[i] = real_elt(par->pv[0], i % orig_size);
819
            /* Get rid of negative draws. */
820
0
            if (pattern[0] < 0)
821
0
                offset -= pattern[0],
822
0
                    pattern[size - 1] += pattern[0],
823
0
                    pattern[1] += pattern[0], pattern[0] = -pattern[0];
824
0
            for (i = 2; i < size; i += 2)
825
0
                if (pattern[i] < 0)
826
0
                    pattern[i - 1] += pattern[i],
827
0
                        pattern[i + 1] += pattern[i],
828
0
                        pattern[i] = -pattern[i];
829
            /*
830
             * Now propagate negative skips iteratively.  Since each step
831
             * decreases either the remaining total of negative skips or
832
             * the total number of pattern elements, the process is
833
             * guaranteed to terminate.
834
             */
835
0
          elim:for (i = 0; i < size; i += 2) {
836
0
                float draw = pattern[i], skip = pattern[i + 1];
837
0
                int inext, iprev;
838
0
                float next, prev;
839
840
0
                if (skip > 0)
841
0
                    continue;
842
0
                if (size == 2) {        /* => i == 0 */
843
0
                    if ((pattern[0] = draw + skip) <= 0)
844
0
                        pattern[0] = -pattern[0];
845
0
                    pattern[1] = 0;
846
0
                    break;
847
0
                }
848
0
                inext = (i == size - 2 ? 0 : i + 2);
849
0
                next = pattern[inext];
850
                /*
851
                 * Consider the sequence D, -S, E, where D and E are draws
852
                 * and -S is a negative skip.  If S <= D, replace the 3
853
                 * elements with D - S + E.
854
                 */
855
0
                if (draw + skip >= 0) {
856
0
                    next += draw + skip;
857
0
                    goto shrink;
858
0
                }
859
                /*
860
                 * Otherwise, let T be the skip preceding D.  Replace T
861
                 * with T + D - S.  If S > E, replace D, -S, E with E, S -
862
                 * (D + E), D; otherwise, replace D, -S, E with E.  In
863
                 * both cases, net progress has occurred.
864
                 */
865
0
                iprev = (i == 0 ? size - 1 : i - 1);
866
0
                prev = pattern[iprev];
867
0
                pattern[iprev] = prev + draw + skip;
868
0
                if (-skip > next) {
869
0
                    pattern[i] = next;
870
0
                    pattern[i + 1] = -(skip + draw + next);
871
0
                    pattern[i + 2] = draw;
872
0
                    goto elim;
873
0
                }
874
0
              shrink:if (inext == 0) {
875
0
                    offset += next - pattern[0];
876
0
                    pattern[0] = next;
877
0
                } else {
878
0
                    pattern[i] = next;
879
0
                    memmove(&pattern[i + 1], &pattern[i + 3],
880
0
                            (size - (i + 3)) * sizeof(pattern[0]));
881
0
                }
882
0
                size -= 2;
883
0
                goto elim;
884
0
            }
885
0
        }
886
0
        code = gs_setdash(pgs, pattern, size, offset);
887
0
        if (code < 0)
888
0
            return code;
889
        /* patterns with 0 total skip length are treated as solid
890
           line pattern on the LJ6 */
891
0
        {
892
0
            bool skips_have_length = false;
893
0
            int i;
894
895
0
            for (i = 0; i < size; i += 2)
896
0
                if (pattern[i + 1] != 0) {
897
0
                    skips_have_length = true;
898
0
                    break;
899
0
                }
900
0
            if (skips_have_length == false) {
901
0
                pxgs->dashed = false;
902
0
                return gs_setdash(pgs, NULL, 0, 0.0);
903
0
            }
904
0
            pxgs->dashed = (size != 0);
905
0
        }
906
0
        gs_currentmatrix(pgs, &pxgs->dash_matrix);
907
0
        return 0;
908
10
    } else if (par->pv[2]) {
909
7
        if (par->pv[1])
910
0
            return_error(errorIllegalAttributeCombination);
911
7
        pxgs->dashed = false;
912
7
        return gs_setdash(pgs, NULL, 0, 0.0);
913
7
    } else
914
3
        return_error(errorMissingAttribute);
915
10
}
916
917
const byte apxSetLineCap[] = {
918
    pxaLineCapStyle, 0, 0
919
};
920
int
921
pxSetLineCap(px_args_t * par, px_state_t * pxs)
922
1.12k
{
923
1.12k
    static const gs_line_cap cap_map[] = pxeLineCap_to_library;
924
925
1.12k
    return gs_setlinecap(pxs->pgs, cap_map[par->pv[0]->value.i]);
926
1.12k
}
927
928
const byte apxBeginUserDefinedLineCap[] = { 0, 0 };
929
int
930
pxBeginUserDefinedLineCap(px_args_t * par, px_state_t * pxs)
931
3
{
932
3
    dmprintf(pxs->memory, "undocumented\n");
933
3
    return 0;
934
3
}
935
936
const byte apxEndUserDefinedLineCap[] = { 0, 0 };
937
int
938
pxEndUserDefinedLineCap(px_args_t * par, px_state_t * pxs)
939
3
{
940
3
    dmprintf(pxs->memory, "undocumented\n");
941
3
    return 0;
942
3
}
943
944
const byte apxSetLineJoin[] = {
945
    pxaLineJoinStyle, 0, 0
946
};
947
948
int
949
pxSetLineJoin(px_args_t * par, px_state_t * pxs)
950
365
{
951
365
    static const gs_line_join join_map[] = pxeLineJoin_to_library;
952
953
365
    return gs_setlinejoin(pxs->pgs, join_map[par->pv[0]->value.i]);
954
365
}
955
956
const byte apxSetMiterLimit[] = {
957
    pxaMiterLength, 0, 0
958
};
959
960
int
961
pxSetMiterLimit(px_args_t * par, px_state_t * pxs)
962
7
{
963
7
    float limit = real_value(par->pv[0], 0);
964
965
7
    if (limit == 0) {           /*
966
                                 * H-P printers interpret this to mean use the default value
967
                                 * of 10, even though nothing in the documentation says or
968
                                 * implies this.
969
                                 */
970
0
        limit = 10;
971
7
    } else {                    /* PCL XL, but not the library, allows limit values <1. */
972
7
        if (limit < 1)
973
0
            limit = 1;
974
7
    }
975
7
    return gs_setmiterlimit(pxs->pgs, limit);
976
7
}
977
978
const byte apxSetPageDefaultCTM[] = { 0, 0 };
979
int
980
pxSetPageDefaultCTM(px_args_t * par, px_state_t * pxs)
981
371
{
982
371
    gs_make_identity(&pxs->pxgs->text_ctm);
983
371
    return gs_setmatrix(pxs->pgs, &pxs->initial_matrix);
984
371
}
985
986
const byte apxSetPageOrigin[] = {
987
    pxaPageOrigin, 0, 0
988
};
989
int
990
pxSetPageOrigin(px_args_t * par, px_state_t * pxs)
991
1.40k
{
992
1.40k
    return gs_translate(pxs->pgs, real_value(par->pv[0], 0),
993
1.40k
                        real_value(par->pv[0], 1));
994
1.40k
}
995
996
const byte apxSetPageRotation[] = {
997
    pxaPageAngle, 0, 0
998
};
999
int
1000
pxSetPageRotation(px_args_t * par, px_state_t * pxs)
1001
236
{                               /* Since the Y coordinate of user space is inverted, */
1002
    /* we must negate rotation angles. */
1003
236
    real angle = -real_value(par->pv[0], 0);
1004
236
    int code = gs_rotate(pxs->pgs, angle);
1005
1006
236
    if (code < 0)
1007
0
        return code;
1008
    /* Post-multiply the text CTM by the rotation matrix. */
1009
236
    {
1010
236
        gs_matrix rmat;
1011
236
        px_gstate_t *pxgs = pxs->pxgs;
1012
1013
236
        gs_make_rotation(angle, &rmat);
1014
236
        gs_matrix_multiply(&pxgs->text_ctm, &rmat, &pxgs->text_ctm);
1015
236
    }
1016
236
    return 0;
1017
236
}
1018
1019
const byte apxSetPageScale[] = {
1020
    0, pxaPageScale, pxaMeasure, pxaUnitsPerMeasure, 0
1021
};
1022
1023
int
1024
pxSetPageScale(px_args_t * par, px_state_t * pxs)
1025
361
{
1026
361
    int code;
1027
361
    real sx = 1;
1028
361
    real sy = 1;
1029
361
    static const real units_conversion_table[3][3] = {
1030
361
        {1, 25.4f, 254},        /* in -> in, mill, 1/10 mill */
1031
361
        {0.0394f, 1, 10},       /* mill -> in, mill, 1/10 mill */
1032
361
        {0.00394f, .1f, 1}      /* 1/10 mill -> in, mill, 1/10 mill */
1033
361
    };
1034
1035
    /* measuure and units of measure.  Actually session user units
1036
       divided by new user unit, bizarre. */
1037
361
    if (par->pv[1] && par->pv[2]) {
1038
        /* new user measure */
1039
0
        real nux = real_value(par->pv[2], 0);
1040
0
        real nuy = real_value(par->pv[2], 1);
1041
1042
0
        if (nux != 0 && nuy != 0) {
1043
            /* new measure */
1044
0
            pxeMeasure_t mt = par->pv[1]->value.i;
1045
            /* convert to session units */
1046
0
            real factor = units_conversion_table[pxs->measure][mt];
1047
0
            real sux = nux * factor;
1048
0
            real suy = nuy * factor;
1049
1050
0
            sx = pxs->units_per_measure.x / sux;
1051
0
            sy = pxs->units_per_measure.y / suy;
1052
            /* check for overflow.  NB we should do a better job here */
1053
0
            if (fabs(sx) > 1000.0) {
1054
0
                dmprintf2(pxs->memory,
1055
0
                          "warning probable overflow avoided for scaling factors %f %f\n",
1056
0
                          sx, sy);
1057
0
                sx = sy = 1;
1058
0
            }
1059
0
        }
1060
361
    } else if (par->pv[0]) {    /* page scale */
1061
233
        sx = real_value(par->pv[0], 0);
1062
233
        sy = real_value(par->pv[0], 1);
1063
233
    }
1064
361
    code = gs_scale(pxs->pgs, sx, sy);
1065
361
    if (code < 0)
1066
0
        return code;
1067
    /* Post-multiply the text CTM by the scale matrix. */
1068
361
    {
1069
361
        gs_matrix smat;
1070
361
        px_gstate_t *pxgs = pxs->pxgs;
1071
1072
361
        gs_make_scaling(sx, sy, &smat);
1073
361
        gs_matrix_multiply(&pxgs->text_ctm, &smat, &pxgs->text_ctm);
1074
361
    }
1075
361
    return 0;
1076
361
}
1077
1078
const byte apxSetPaintTxMode[] = {
1079
    pxaTxMode, 0, 0
1080
};
1081
int
1082
pxSetPaintTxMode(px_args_t * par, px_state_t * pxs)
1083
235
{
1084
235
    gs_settexturetransparent(pxs->pgs, par->pv[0]->value.i == eTransparent);
1085
235
    return 0;
1086
235
}
1087
1088
const byte apxSetPenWidth[] = {
1089
    pxaPenWidth, 0, 0
1090
};
1091
int
1092
pxSetPenWidth(px_args_t * par, px_state_t * pxs)
1093
7
{
1094
7
    return gs_setlinewidth(pxs->pgs, real_value(par->pv[0], 0));
1095
7
}
1096
1097
const byte apxSetROP[] = {
1098
    pxaROP3, 0, 0
1099
};
1100
int
1101
pxSetROP(px_args_t * par, px_state_t * pxs)
1102
594
{
1103
    /* 252 here is a magic number. It is the default state of RasterOPs which appears to be
1104
     * fully opaque. Since it's the default, and that seems to work, permit it to be set.
1105
     */
1106
594
    if (pxs->high_level_device && !pxs->supports_rasterops && par->pv[0]->value.i != 252) {
1107
0
        errprintf(pxs->memory, "Unsupported use of RasterOP %d detected. Output may not be correct.\n", par->pv[0]->value.i);
1108
0
        return 0;
1109
0
    }
1110
594
    gs_setrasterop(pxs->pgs, (gs_rop3_t) (par->pv[0]->value.i));
1111
594
    return 0;
1112
594
}
1113
1114
const byte apxSetSourceTxMode[] = {
1115
    pxaTxMode, 0, 0
1116
};
1117
int
1118
pxSetSourceTxMode(px_args_t * par, px_state_t * pxs)
1119
235
{
1120
235
    gs_setsourcetransparent(pxs->pgs, par->pv[0]->value.i == eTransparent);
1121
235
    return 0;
1122
235
}
1123
1124
const byte apxSetCharBoldValue[] = {
1125
    pxaCharBoldValue, 0, 0
1126
};
1127
int
1128
pxSetCharBoldValue(px_args_t * par, px_state_t * pxs)
1129
0
{
1130
0
    float arg = real_value(par->pv[0], 0);
1131
1132
0
    pxs->pxgs->char_bold_value = arg;
1133
0
    return 0;
1134
0
}
1135
1136
const byte apxSetClipMode[] = {
1137
    pxaClipMode, 0, 0
1138
};
1139
int
1140
pxSetClipMode(px_args_t * par, px_state_t * pxs)
1141
495
{
1142
495
    pxs->pxgs->clip_mode = par->pv[0]->value.i;
1143
495
    return 0;
1144
495
}
1145
1146
const byte apxSetPathToClip[] = { 0, 0 };
1147
int
1148
pxSetPathToClip(px_args_t * par, px_state_t * pxs)
1149
3
{
1150
3
    return gs_clippath(pxs->pgs);
1151
3
}
1152
1153
const byte apxSetCharSubMode[] = {
1154
    pxaCharSubModeArray, 0, 0
1155
};
1156
int
1157
pxSetCharSubMode(px_args_t * par, px_state_t * pxs)
1158
0
{
1159
    /*
1160
     * It isn't clear from the documentation why the attribute is an
1161
     * array rather than just a Boolean, but we have to assume there
1162
     * is some reason for this.
1163
     */
1164
0
    const px_value_t *psubs = par->pv[0];
1165
0
    pxeCharSubModeArray_t arg;
1166
1167
0
    if (psubs->value.array.size != 1 ||
1168
0
        psubs->value.array.data[0] >= pxeCharSubModeArray_next)
1169
0
        return_error(errorIllegalAttributeValue);
1170
0
    arg = psubs->value.array.data[0];
1171
0
    if (pxs->pxgs->char_sub_mode != arg) {
1172
0
        pxs->pxgs->char_sub_mode = arg;
1173
0
        px_purge_character_cache(pxs);
1174
0
    }
1175
0
    return 0;
1176
0
}