Coverage Report

Created: 2026-09-14 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/xps/xpstile.c
Line
Count
Source
1
/* Copyright (C) 2001-2026 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
/* XPS interpreter - tiles for pattern rendering */
18
19
#include "ghostxps.h"
20
#include "gxdevsop.h"
21
22
/*
23
 * Parse a tiling brush (visual and image brushes at this time) common
24
 * properties. Use the callback to draw the individual tiles.
25
 */
26
27
enum { TILE_NONE, TILE_TILE, TILE_FLIP_X, TILE_FLIP_Y, TILE_FLIP_X_Y };
28
29
struct tile_closure_s
30
{
31
    xps_context_t *ctx;
32
    char *base_uri;
33
    xps_resource_t *dict;
34
    xps_item_t *tag;
35
    gs_rect viewbox;
36
    int tile_mode;
37
    void *user;
38
    int (*func)(xps_context_t*, char*, xps_resource_t*, xps_item_t*, void*);
39
};
40
41
static int
42
xps_paint_tiling_brush_clipped(struct tile_closure_s *c)
43
0
{
44
0
    xps_context_t *ctx = c->ctx;
45
0
    int code;
46
47
0
    gs_moveto(ctx->pgs, c->viewbox.p.x, c->viewbox.p.y);
48
0
    gs_lineto(ctx->pgs, c->viewbox.p.x, c->viewbox.q.y);
49
0
    gs_lineto(ctx->pgs, c->viewbox.q.x, c->viewbox.q.y);
50
0
    gs_lineto(ctx->pgs, c->viewbox.q.x, c->viewbox.p.y);
51
0
    gs_closepath(ctx->pgs);
52
0
    gs_clip(ctx->pgs);
53
0
    gs_newpath(ctx->pgs);
54
55
0
    code = c->func(c->ctx, c->base_uri, c->dict, c->tag, c->user);
56
0
    if (code < 0)
57
0
        return gs_rethrow(code, "cannot draw clipped tile");
58
59
0
    return 0;
60
0
}
61
62
static int
63
xps_paint_tiling_brush(const gs_client_color *pcc, gs_gstate *pgs)
64
0
{
65
0
    struct tile_closure_s *c = pcc->pattern->client_data;
66
0
    xps_context_t *ctx = c->ctx;
67
0
    gs_gstate *saved_pgs;
68
0
    int code;
69
70
0
    saved_pgs = ctx->pgs;
71
0
    ctx->pgs = pgs;
72
73
0
    gs_gsave(ctx->pgs);
74
0
    code = xps_paint_tiling_brush_clipped(c);
75
0
    if (code)
76
0
        goto cleanup;
77
0
    gs_grestore(ctx->pgs);
78
79
0
    if (c->tile_mode == TILE_FLIP_X || c->tile_mode == TILE_FLIP_X_Y)
80
0
    {
81
0
        gs_gsave(ctx->pgs);
82
0
        gs_translate(ctx->pgs, c->viewbox.q.x * 2, 0.0);
83
0
        gs_scale(ctx->pgs, -1.0, 1.0);
84
0
        code = xps_paint_tiling_brush_clipped(c);
85
0
        if (code)
86
0
            goto cleanup;
87
0
        gs_grestore(ctx->pgs);
88
0
    }
89
90
0
    if (c->tile_mode == TILE_FLIP_Y || c->tile_mode == TILE_FLIP_X_Y)
91
0
    {
92
0
        gs_gsave(ctx->pgs);
93
0
        gs_translate(ctx->pgs, 0.0, c->viewbox.q.y * 2);
94
0
        gs_scale(ctx->pgs, 1.0, -1.0);
95
0
        code = xps_paint_tiling_brush_clipped(c);
96
0
        if (code)
97
0
            goto cleanup;
98
0
        gs_grestore(ctx->pgs);
99
0
    }
100
101
0
    if (c->tile_mode == TILE_FLIP_X_Y)
102
0
    {
103
0
        gs_gsave(ctx->pgs);
104
0
        gs_translate(ctx->pgs, c->viewbox.q.x * 2, c->viewbox.q.y * 2);
105
0
        gs_scale(ctx->pgs, -1.0, -1.0);
106
0
        code = xps_paint_tiling_brush_clipped(c);
107
0
        if (code)
108
0
            goto cleanup;
109
0
        gs_grestore(ctx->pgs);
110
0
    }
111
112
0
    ctx->pgs = saved_pgs;
113
114
0
    return 0;
115
116
0
cleanup:
117
0
    gs_grestore(ctx->pgs);
118
0
    ctx->pgs = saved_pgs;
119
0
    return gs_rethrow(code, "cannot draw tile");
120
0
}
121
122
int
123
xps_high_level_pattern(xps_context_t *ctx)
124
0
{
125
0
    gs_matrix m;
126
0
    gs_rect bbox;
127
0
    gs_fixed_rect clip_box;
128
0
    int code, code1;
129
0
    gx_device_color *pdc = gs_currentdevicecolor_inline(ctx->pgs);
130
0
    const gs_client_pattern *ppat = gs_getpattern(&pdc->ccolor);
131
0
    gs_pattern1_instance_t *pinst =
132
0
        (gs_pattern1_instance_t *)gs_currentcolor(ctx->pgs)->pattern;
133
134
0
    code = gx_pattern_cache_add_dummy_entry(ctx->pgs, pinst,
135
0
        ctx->pgs->device->color_info.depth);
136
0
    if (code < 0)
137
0
        return code;
138
139
0
    code = gs_gsave(ctx->pgs);
140
0
    if (code < 0)
141
0
        return code;
142
143
0
    dev_proc(ctx->pgs->device, get_initial_matrix)(ctx->pgs->device, &m);
144
0
    gs_setmatrix(ctx->pgs, &m);
145
0
    code = gs_bbox_transform(&ppat->BBox, &ctm_only(ctx->pgs), &bbox);
146
0
    if (code < 0) {
147
0
        gs_grestore(ctx->pgs);
148
0
        return code;
149
0
    }
150
0
    clip_box.p.x = float2fixed(bbox.p.x);
151
0
    clip_box.p.y = float2fixed(bbox.p.y);
152
0
    clip_box.q.x = float2fixed(bbox.q.x);
153
0
    clip_box.q.y = float2fixed(bbox.q.y);
154
0
    code = gx_clip_to_rectangle(ctx->pgs, &clip_box);
155
0
    if (code < 0) {
156
0
        gs_grestore(ctx->pgs);
157
0
        return code;
158
0
    }
159
160
0
    {
161
0
        pattern_accum_param_s param;
162
0
        param.pinst = (void *)pinst;
163
0
        param.graphics_state = (void *)ctx->pgs;
164
0
        param.pinst_id = pinst->id;
165
166
0
        code = (*dev_proc(ctx->pgs->device, dev_spec_op))((gx_device *)ctx->pgs->device,
167
0
            gxdso_pattern_start_accum, &param, sizeof(pattern_accum_param_s));
168
0
    }
169
170
0
    if (code < 0) {
171
0
        gs_grestore(ctx->pgs);
172
0
        return code;
173
0
    }
174
175
0
    code = xps_paint_tiling_brush(&pdc->ccolor, ctx->pgs);
176
0
    if (code)
177
0
        gs_rethrow(code, "high level pattern brush function failed");
178
179
0
    code1 = gs_grestore(ctx->pgs);
180
0
    if (code >= 0)
181
0
        code = code1;
182
183
0
    {
184
0
        pattern_accum_param_s param;
185
0
        param.pinst = (void *)pinst;
186
0
        param.graphics_state = (void *)ctx->pgs;
187
0
        param.pinst_id = pinst->id;
188
189
0
        code1 = (*dev_proc(ctx->pgs->device, dev_spec_op))((gx_device *)ctx->pgs->device,
190
0
            gxdso_pattern_finish_accum, &param, sizeof(pattern_accum_param_s));
191
0
        if (code >= 0)
192
0
            code = code1;
193
0
    }
194
195
0
    return code;
196
0
}
197
198
static int
199
xps_remap_pattern(const gs_client_color *pcc, gs_gstate *pgs)
200
0
{
201
0
    gs_client_pattern *ppat = (gs_client_pattern *)gs_getpattern(pcc);
202
0
    struct tile_closure_s *c = pcc->pattern->client_data;
203
0
    xps_context_t *ctx = c->ctx;
204
0
    int code;
205
206
    /* pgs->device is the newly created pattern accumulator, but we want to test the device
207
     * that is 'behind' that, the actual output device, so we use the one from
208
     * the saved XPS graphics state.
209
     */
210
0
    code = dev_proc(ctx->pgs->device, dev_spec_op)(ctx->pgs->device,
211
0
                                gxdso_pattern_can_accum, ppat, ppat->uid.id);
212
213
0
    if (code == 1) {
214
        /* Device handles high-level patterns, so return 'remap'.
215
         * This closes the internal accumulator device, as we no longer need
216
         * it, and the error trickles back up to the PDL client. The client
217
         * must then take action to start the device's accumulator, draw the
218
         * pattern, close the device's accumulator and generate a cache entry.
219
         */
220
0
        return gs_error_Remap_Color;
221
0
    } else {
222
0
        code = xps_paint_tiling_brush(pcc, pgs);
223
0
        if (code)
224
0
            return gs_rethrow(code, "remap pattern brush function failed");
225
0
        return 0;
226
0
    }
227
0
}
228
229
int
230
xps_parse_tiling_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root,
231
    int (*func)(xps_context_t*, char*, xps_resource_t*, xps_item_t*, void*), void *user)
232
13
{
233
13
    xps_item_t *node;
234
13
    int code;
235
236
13
    char *opacity_att;
237
13
    char *transform_att;
238
13
    char *viewbox_att;
239
13
    char *viewport_att;
240
13
    char *tile_mode_att;
241
    /*char *viewbox_units_att;*/
242
    /*char *viewport_units_att;*/
243
244
13
    xps_item_t *transform_tag = NULL;
245
246
13
    gs_matrix transform;
247
13
    gs_rect viewbox;
248
13
    gs_rect viewport;
249
13
    float scalex, scaley;
250
13
    int tile_mode;
251
252
13
    opacity_att = xps_att(root, "Opacity");
253
13
    transform_att = xps_att(root, "Transform");
254
13
    viewbox_att = xps_att(root, "Viewbox");
255
13
    viewport_att = xps_att(root, "Viewport");
256
13
    tile_mode_att = xps_att(root, "TileMode");
257
    /*viewbox_units_att = xps_att(root, "ViewboxUnits");*/
258
    /*viewport_units_att = xps_att(root, "ViewportUnits");*/
259
260
13
    for (node = xps_down(root); node; node = xps_next(node))
261
0
    {
262
0
        if (!strcmp(xps_tag(node), "ImageBrush.Transform"))
263
0
            transform_tag = xps_down(node);
264
0
        if (!strcmp(xps_tag(node), "VisualBrush.Transform"))
265
0
            transform_tag = xps_down(node);
266
0
    }
267
268
13
    xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
269
270
13
    gs_make_identity(&transform);
271
13
    if (transform_att)
272
0
        xps_parse_render_transform(ctx, transform_att, &transform);
273
13
    if (transform_tag)
274
0
        xps_parse_matrix_transform(ctx, transform_tag, &transform);
275
276
13
    viewbox.p.x = 0.0; viewbox.p.y = 0.0;
277
13
    viewbox.q.x = 1.0; viewbox.q.y = 1.0;
278
13
    if (viewbox_att)
279
0
        xps_parse_rectangle(ctx, viewbox_att, &viewbox);
280
281
13
    viewport.p.x = 0.0; viewport.p.y = 0.0;
282
13
    viewport.q.x = 1.0; viewport.q.y = 1.0;
283
13
    if (viewport_att)
284
13
        xps_parse_rectangle(ctx, viewport_att, &viewport);
285
286
    /* some sanity checks on the viewport/viewbox size */
287
13
    if (fabs(viewport.q.x - viewport.p.x) < 0.01) { gs_warn("skipping tile with zero width view port"); return 0; }
288
13
    if (fabs(viewport.q.y - viewport.p.y) < 0.01) { gs_warn("skipping tile with zero height view port"); return 0; }
289
13
    if (fabs(viewbox.q.x - viewbox.p.x) < 0.01) { gs_warn("skipping tile with zero width view box"); return 0; }
290
13
    if (fabs(viewbox.q.y - viewbox.p.y) < 0.01) { gs_warn("skipping tile with zero height view box"); return 0; }
291
292
13
    scalex = (viewport.q.x - viewport.p.x) / (viewbox.q.x - viewbox.p.x);
293
13
    scaley = (viewport.q.y - viewport.p.y) / (viewbox.q.y - viewbox.p.y);
294
295
13
    tile_mode = TILE_NONE;
296
13
    if (tile_mode_att)
297
13
    {
298
13
        if (!strcmp(tile_mode_att, "None"))
299
0
            tile_mode = TILE_NONE;
300
13
        if (!strcmp(tile_mode_att, "Tile"))
301
10
            tile_mode = TILE_TILE;
302
13
        if (!strcmp(tile_mode_att, "FlipX"))
303
3
            tile_mode = TILE_FLIP_X;
304
13
        if (!strcmp(tile_mode_att, "FlipY"))
305
0
            tile_mode = TILE_FLIP_Y;
306
13
        if (!strcmp(tile_mode_att, "FlipXY"))
307
0
            tile_mode = TILE_FLIP_X_Y;
308
13
    }
309
310
13
    gs_gsave(ctx->pgs);
311
312
13
    code = xps_begin_opacity(ctx, base_uri, dict, opacity_att, NULL, false, false);
313
13
    if (code)
314
0
    {
315
0
        gs_grestore(ctx->pgs);
316
0
        return gs_rethrow(code, "cannot create transparency group");
317
0
    }
318
319
    /* TODO(tor): check viewport and tiling to see if we can set it to TILE_NONE */
320
321
13
    if (tile_mode != TILE_NONE)
322
13
    {
323
13
        struct tile_closure_s closure;
324
325
13
        gs_client_pattern gspat;
326
13
        gs_client_color gscolor;
327
13
        gs_color_space *cs;
328
13
        bool sa;
329
13
        float opacity;
330
331
13
        closure.ctx = ctx;
332
13
        closure.base_uri = base_uri;
333
13
        closure.dict = dict;
334
13
        closure.tag = root;
335
13
        closure.tile_mode = tile_mode;
336
13
        closure.user = user;
337
13
        closure.func = func;
338
339
13
        closure.viewbox.p.x = viewbox.p.x;
340
13
        closure.viewbox.p.y = viewbox.p.y;
341
13
        closure.viewbox.q.x = viewbox.q.x;
342
13
        closure.viewbox.q.y = viewbox.q.y;
343
344
13
        gs_pattern1_init(&gspat);
345
13
        uid_set_UniqueID(&gspat.uid, gs_next_ids(ctx->memory, 1));
346
13
        gspat.PaintType = 1;
347
13
        gspat.TilingType = 2;
348
13
        gspat.PaintProc = xps_remap_pattern;
349
350
        /* We need to know if this tiling brush includes transparency.
351
           We could do a proper scan, but for now we'll be lazy and just look
352
           at the flag from scanning the page. */
353
13
        gspat.uses_transparency = ctx->has_transparency;
354
355
13
        gspat.XStep = viewbox.q.x - viewbox.p.x;
356
13
        gspat.YStep = viewbox.q.y - viewbox.p.y;
357
13
        gspat.BBox.p.x = viewbox.p.x;
358
13
        gspat.BBox.p.y = viewbox.p.y;
359
13
        gspat.BBox.q.x = viewbox.q.x;
360
13
        gspat.BBox.q.y = viewbox.q.y;
361
362
13
        if (tile_mode == TILE_FLIP_X || tile_mode == TILE_FLIP_X_Y)
363
3
        {
364
3
            gspat.BBox.q.x += gspat.XStep;
365
3
            gspat.XStep *= 2;
366
3
        }
367
368
13
        if (tile_mode == TILE_FLIP_Y || tile_mode == TILE_FLIP_X_Y)
369
0
        {
370
0
            gspat.BBox.q.y += gspat.YStep;
371
0
            gspat.YStep *= 2;
372
0
        }
373
374
13
        gs_matrix_translate(&transform, viewport.p.x, viewport.p.y, &transform);
375
13
        gs_matrix_scale(&transform, scalex, scaley, &transform);
376
13
        gs_matrix_translate(&transform, -viewbox.p.x, -viewbox.p.y, &transform);
377
378
13
        cs = ctx->srgb;
379
13
        code = gs_setcolorspace(ctx->pgs, cs);
380
13
        if (code < 0) {
381
0
            xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
382
0
            gs_grestore(ctx->pgs);
383
0
            return gs_rethrow(code, "cannot draw tile");
384
0
        }
385
13
        gsicc_adjust_profile_rc(cs->cmm_icc_profile_data, 1, "xps_parse_tiling_brush");
386
387
13
        sa = gs_currentstrokeadjust(ctx->pgs);
388
13
        gs_setstrokeadjust(ctx->pgs, false);
389
13
        gs_makepattern(&gscolor, &gspat, &transform, ctx->pgs, NULL);
390
13
        if (gscolor.pattern == NULL) {
391
0
            xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
392
0
            gs_grestore(ctx->pgs);
393
0
            return gs_rethrow(gs_error_unknownerror, "cannot draw tile");
394
0
        }
395
13
        gscolor.pattern->client_data = &closure;
396
13
        code = gs_setpattern(ctx->pgs, &gscolor);
397
13
        if (code < 0) {
398
0
            xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
399
0
            gs_grestore(ctx->pgs);
400
0
            return gs_rethrow(code, "cannot draw tile");
401
0
        }
402
        /* If the tiling brush has an opacity, it was already set in the group
403
           that we are filling.  Reset to 1.0 here to avoid double application
404
           when the tiling actually occurs */
405
13
        opacity = gs_getfillconstantalpha(ctx->pgs);
406
13
        gs_setfillconstantalpha(ctx->pgs, 1.0);
407
13
        gs_setstrokeconstantalpha(ctx->pgs, 1.0);
408
13
        xps_fill(ctx);
409
13
        gs_setfillconstantalpha(ctx->pgs, opacity);
410
13
        gs_setstrokeconstantalpha(ctx->pgs, opacity);
411
412
13
        gs_setstrokeadjust(ctx->pgs, sa);
413
13
        gsicc_adjust_profile_rc(cs->cmm_icc_profile_data, -1, "xps_parse_tiling_brush");
414
415
        /* gs_makepattern increments the pattern count stored in the color
416
         * structure. We will discard the color struct (its on the stack)
417
         * so we need to decrement the reference before we throw away
418
         * the structure.
419
         */
420
13
        gs_pattern_reference(&gscolor, -1);
421
13
    }
422
0
    else
423
0
    {
424
0
        xps_clip(ctx);
425
426
0
        gs_concat(ctx->pgs, &transform);
427
428
0
        gs_translate(ctx->pgs, viewport.p.x, viewport.p.y);
429
0
        gs_scale(ctx->pgs, scalex, scaley);
430
0
        gs_translate(ctx->pgs, -viewbox.p.x, -viewbox.p.y);
431
432
0
        gs_moveto(ctx->pgs, viewbox.p.x, viewbox.p.y);
433
0
        gs_lineto(ctx->pgs, viewbox.p.x, viewbox.q.y);
434
0
        gs_lineto(ctx->pgs, viewbox.q.x, viewbox.q.y);
435
0
        gs_lineto(ctx->pgs, viewbox.q.x, viewbox.p.y);
436
0
        gs_closepath(ctx->pgs);
437
0
        gs_clip(ctx->pgs);
438
0
        gs_newpath(ctx->pgs);
439
440
0
        code = func(ctx, base_uri, dict, root, user);
441
0
        if (code < 0)
442
0
        {
443
0
            xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
444
0
            gs_grestore(ctx->pgs);
445
0
            return gs_rethrow(code, "cannot draw tile");
446
0
        }
447
0
    }
448
449
13
    xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
450
451
13
    gs_grestore(ctx->pgs);
452
453
13
    return 0;
454
13
}