Coverage Report

Created: 2026-04-01 07:17

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
0
{
233
0
    xps_item_t *node;
234
0
    int code;
235
236
0
    char *opacity_att;
237
0
    char *transform_att;
238
0
    char *viewbox_att;
239
0
    char *viewport_att;
240
0
    char *tile_mode_att;
241
    /*char *viewbox_units_att;*/
242
    /*char *viewport_units_att;*/
243
244
0
    xps_item_t *transform_tag = NULL;
245
246
0
    gs_matrix transform;
247
0
    gs_rect viewbox;
248
0
    gs_rect viewport;
249
0
    float scalex, scaley;
250
0
    int tile_mode;
251
252
0
    opacity_att = xps_att(root, "Opacity");
253
0
    transform_att = xps_att(root, "Transform");
254
0
    viewbox_att = xps_att(root, "Viewbox");
255
0
    viewport_att = xps_att(root, "Viewport");
256
0
    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
0
    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
0
    xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
269
270
0
    gs_make_identity(&transform);
271
0
    if (transform_att)
272
0
        xps_parse_render_transform(ctx, transform_att, &transform);
273
0
    if (transform_tag)
274
0
        xps_parse_matrix_transform(ctx, transform_tag, &transform);
275
276
0
    viewbox.p.x = 0.0; viewbox.p.y = 0.0;
277
0
    viewbox.q.x = 1.0; viewbox.q.y = 1.0;
278
0
    if (viewbox_att)
279
0
        xps_parse_rectangle(ctx, viewbox_att, &viewbox);
280
281
0
    viewport.p.x = 0.0; viewport.p.y = 0.0;
282
0
    viewport.q.x = 1.0; viewport.q.y = 1.0;
283
0
    if (viewport_att)
284
0
        xps_parse_rectangle(ctx, viewport_att, &viewport);
285
286
    /* some sanity checks on the viewport/viewbox size */
287
0
    if (fabs(viewport.q.x - viewport.p.x) < 0.01) { gs_warn("skipping tile with zero width view port"); return 0; }
288
0
    if (fabs(viewport.q.y - viewport.p.y) < 0.01) { gs_warn("skipping tile with zero height view port"); return 0; }
289
0
    if (fabs(viewbox.q.x - viewbox.p.x) < 0.01) { gs_warn("skipping tile with zero width view box"); return 0; }
290
0
    if (fabs(viewbox.q.y - viewbox.p.y) < 0.01) { gs_warn("skipping tile with zero height view box"); return 0; }
291
292
0
    scalex = (viewport.q.x - viewport.p.x) / (viewbox.q.x - viewbox.p.x);
293
0
    scaley = (viewport.q.y - viewport.p.y) / (viewbox.q.y - viewbox.p.y);
294
295
0
    tile_mode = TILE_NONE;
296
0
    if (tile_mode_att)
297
0
    {
298
0
        if (!strcmp(tile_mode_att, "None"))
299
0
            tile_mode = TILE_NONE;
300
0
        if (!strcmp(tile_mode_att, "Tile"))
301
0
            tile_mode = TILE_TILE;
302
0
        if (!strcmp(tile_mode_att, "FlipX"))
303
0
            tile_mode = TILE_FLIP_X;
304
0
        if (!strcmp(tile_mode_att, "FlipY"))
305
0
            tile_mode = TILE_FLIP_Y;
306
0
        if (!strcmp(tile_mode_att, "FlipXY"))
307
0
            tile_mode = TILE_FLIP_X_Y;
308
0
    }
309
310
0
    gs_gsave(ctx->pgs);
311
312
0
    code = xps_begin_opacity(ctx, base_uri, dict, opacity_att, NULL, false, false);
313
0
    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
0
    if (tile_mode != TILE_NONE)
322
0
    {
323
0
        struct tile_closure_s closure;
324
325
0
        gs_client_pattern gspat;
326
0
        gs_client_color gscolor;
327
0
        gs_color_space *cs;
328
0
        bool sa;
329
0
        float opacity;
330
331
0
        closure.ctx = ctx;
332
0
        closure.base_uri = base_uri;
333
0
        closure.dict = dict;
334
0
        closure.tag = root;
335
0
        closure.tile_mode = tile_mode;
336
0
        closure.user = user;
337
0
        closure.func = func;
338
339
0
        closure.viewbox.p.x = viewbox.p.x;
340
0
        closure.viewbox.p.y = viewbox.p.y;
341
0
        closure.viewbox.q.x = viewbox.q.x;
342
0
        closure.viewbox.q.y = viewbox.q.y;
343
344
0
        gs_pattern1_init(&gspat);
345
0
        uid_set_UniqueID(&gspat.uid, gs_next_ids(ctx->memory, 1));
346
0
        gspat.PaintType = 1;
347
0
        gspat.TilingType = 2;
348
0
        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
0
        gspat.uses_transparency = ctx->has_transparency;
354
355
0
        gspat.XStep = viewbox.q.x - viewbox.p.x;
356
0
        gspat.YStep = viewbox.q.y - viewbox.p.y;
357
0
        gspat.BBox.p.x = viewbox.p.x;
358
0
        gspat.BBox.p.y = viewbox.p.y;
359
0
        gspat.BBox.q.x = viewbox.q.x;
360
0
        gspat.BBox.q.y = viewbox.q.y;
361
362
0
        if (tile_mode == TILE_FLIP_X || tile_mode == TILE_FLIP_X_Y)
363
0
        {
364
0
            gspat.BBox.q.x += gspat.XStep;
365
0
            gspat.XStep *= 2;
366
0
        }
367
368
0
        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
0
        gs_matrix_translate(&transform, viewport.p.x, viewport.p.y, &transform);
375
0
        gs_matrix_scale(&transform, scalex, scaley, &transform);
376
0
        gs_matrix_translate(&transform, -viewbox.p.x, -viewbox.p.y, &transform);
377
378
0
        cs = ctx->srgb;
379
0
        gs_setcolorspace(ctx->pgs, cs);
380
0
        gsicc_adjust_profile_rc(cs->cmm_icc_profile_data, 1, "xps_parse_tiling_brush");
381
382
0
        sa = gs_currentstrokeadjust(ctx->pgs);
383
0
        gs_setstrokeadjust(ctx->pgs, false);
384
0
        gs_makepattern(&gscolor, &gspat, &transform, ctx->pgs, NULL);
385
0
        gscolor.pattern->client_data = &closure;
386
0
        gs_setpattern(ctx->pgs, &gscolor);
387
        /* If the tiling brush has an opacity, it was already set in the group
388
           that we are filling.  Reset to 1.0 here to avoid double application
389
           when the tiling actually occurs */
390
0
        opacity = gs_getfillconstantalpha(ctx->pgs);
391
0
        gs_setfillconstantalpha(ctx->pgs, 1.0);
392
0
        gs_setstrokeconstantalpha(ctx->pgs, 1.0);
393
0
        xps_fill(ctx);
394
0
        gs_setfillconstantalpha(ctx->pgs, opacity);
395
0
        gs_setstrokeconstantalpha(ctx->pgs, opacity);
396
397
0
        gs_setstrokeadjust(ctx->pgs, sa);
398
0
        gsicc_adjust_profile_rc(cs->cmm_icc_profile_data, -1, "xps_parse_tiling_brush");
399
400
        /* gs_makepattern increments the pattern count stored in the color
401
         * structure. We will discard the color struct (its on the stack)
402
         * so we need to decrement the reference before we throw away
403
         * the structure.
404
         */
405
0
        gs_pattern_reference(&gscolor, -1);
406
0
    }
407
0
    else
408
0
    {
409
0
        xps_clip(ctx);
410
411
0
        gs_concat(ctx->pgs, &transform);
412
413
0
        gs_translate(ctx->pgs, viewport.p.x, viewport.p.y);
414
0
        gs_scale(ctx->pgs, scalex, scaley);
415
0
        gs_translate(ctx->pgs, -viewbox.p.x, -viewbox.p.y);
416
417
0
        gs_moveto(ctx->pgs, viewbox.p.x, viewbox.p.y);
418
0
        gs_lineto(ctx->pgs, viewbox.p.x, viewbox.q.y);
419
0
        gs_lineto(ctx->pgs, viewbox.q.x, viewbox.q.y);
420
0
        gs_lineto(ctx->pgs, viewbox.q.x, viewbox.p.y);
421
0
        gs_closepath(ctx->pgs);
422
0
        gs_clip(ctx->pgs);
423
0
        gs_newpath(ctx->pgs);
424
425
0
        code = func(ctx, base_uri, dict, root, user);
426
0
        if (code < 0)
427
0
        {
428
0
            xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
429
0
            gs_grestore(ctx->pgs);
430
0
            return gs_rethrow(code, "cannot draw tile");
431
0
        }
432
0
    }
433
434
0
    xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
435
436
0
    gs_grestore(ctx->pgs);
437
438
0
    return 0;
439
0
}