Coverage Report

Created: 2026-04-01 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/xps/xpspath.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
/* XPS interpreter - path (vector drawing) support */
17
18
#include "ghostxps.h"
19
20
0
#define INITIAL_DASH_SIZE 32
21
0
#define ADDITIVE_DASH_SIZE 1024
22
23
char *
24
xps_get_real_params(char *s, int num, float *x)
25
0
{
26
0
    int k = 0;
27
28
0
    if (s != NULL && *s != 0) {
29
0
        while (*s)
30
0
        {
31
0
            char *s0;
32
0
            while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a)
33
0
                s++;
34
0
            s0 = s;
35
0
            x[k] = (float)strtod(s, &s);
36
0
            if (s == s0)
37
0
                return NULL; /* Failed to read */
38
0
            while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a)
39
0
                s++;
40
0
            if (*s == ',')
41
0
                s++;
42
0
            if (++k == num)
43
0
                break;
44
0
        }
45
0
        return s;
46
0
    } else
47
0
        return NULL;
48
0
}
49
50
char *
51
xps_get_point(char *s_in, float *x, float *y)
52
0
{
53
0
    char *s_out = s_in;
54
0
    float xy[2];
55
56
0
    s_out = xps_get_real_params(s_out, 2, xy);
57
0
    *x = xy[0];
58
0
    *y = xy[1];
59
0
    return s_out;
60
0
}
61
62
void
63
xps_clip(xps_context_t *ctx)
64
3
{
65
3
    if (ctx->fill_rule == 0)
66
3
        gs_eoclip(ctx->pgs);
67
0
    else
68
0
        gs_clip(ctx->pgs);
69
3
    gs_newpath(ctx->pgs);
70
3
}
71
72
void
73
xps_fill(xps_context_t *ctx)
74
2.79k
{
75
2.79k
    if (gs_getfillconstantalpha(ctx->pgs) < 0.001)
76
0
        gs_newpath(ctx->pgs);
77
2.79k
    else if (ctx->fill_rule == 0) {
78
0
        if (gs_eofill(ctx->pgs) == gs_error_Remap_Color){
79
0
            ctx->in_high_level_pattern = true;
80
0
            xps_high_level_pattern(ctx);
81
0
            ctx->in_high_level_pattern = false;
82
0
            gs_eofill(ctx->pgs);
83
0
        }
84
0
    }
85
2.79k
    else {
86
2.79k
        if (gs_fill(ctx->pgs) == gs_error_Remap_Color){
87
0
            ctx->in_high_level_pattern = true;
88
0
            xps_high_level_pattern(ctx);
89
0
            ctx->in_high_level_pattern = false;
90
0
            gs_fill(ctx->pgs);
91
0
        }
92
2.79k
    }
93
2.79k
}
94
95
/* Draw an arc segment transformed by the matrix, we approximate with straight
96
 * line segments. We cannot use the gs_arc function because they only draw
97
 * circular arcs, we need to transform the line to make them elliptical but
98
 * without transforming the line width.
99
 *
100
 * We are guaranteed that on entry the point is at the point that would be
101
 * calculated by th0, and on exit, a point is generated for us at th0.
102
 */
103
static inline void
104
xps_draw_arc_segment(xps_context_t *ctx, gs_matrix *mtx, float th0, float th1, int iscw)
105
0
{
106
0
    float t, d;
107
0
    gs_point p;
108
109
0
    while (th1 < th0)
110
0
        th1 += (float)(M_PI * 2.0);
111
112
0
    d = (float)(1 * (M_PI / 180.0)); /* 1-degree precision */
113
114
0
    if (iscw)
115
0
    {
116
0
        for (t = th0 + d; t < th1 - d/2; t += d)
117
0
        {
118
0
            gs_point_transform(cos(t), sin(t), mtx, &p);
119
0
            gs_lineto(ctx->pgs, p.x, p.y);
120
0
        }
121
0
    }
122
0
    else
123
0
    {
124
0
        th0 += (float)(M_PI * 2);
125
0
        for (t = th0 - d; t > th1 + d/2; t -= d)
126
0
        {
127
0
            gs_point_transform(cos(t), sin(t), mtx, &p);
128
0
            gs_lineto(ctx->pgs, p.x, p.y);
129
0
        }
130
0
    }
131
0
}
132
133
/* Given two vectors find the angle between them. */
134
static inline double
135
angle_between(const gs_point u, const gs_point v)
136
0
{
137
0
    double det = u.x * v.y - u.y * v.x;
138
0
    double sign = (det < 0 ? -1.0 : 1.0);
139
0
    double magu = u.x * u.x + u.y * u.y;
140
0
    double magv = v.x * v.x + v.y * v.y;
141
0
    double udotv = u.x * v.x + u.y * v.y;
142
0
    double t = udotv / (magu * magv);
143
    /* guard against rounding errors when near |1| (where acos will return NaN) */
144
0
    if (t < -1.0) t = -1.0;
145
0
    if (t > 1.0) t = 1.0;
146
0
    return sign * acos(t);
147
0
}
148
149
static void
150
xps_draw_arc(xps_context_t *ctx,
151
        float size_x, float size_y, float rotation_angle,
152
        int is_large_arc, int is_clockwise,
153
        float point_x, float point_y)
154
0
{
155
0
    gs_matrix rotmat, revmat;
156
0
    gs_matrix mtx;
157
0
    gs_point pt;
158
0
    double rx, ry;
159
0
    double x1, y1, x2, y2;
160
0
    double x1t, y1t;
161
0
    double cxt, cyt, cx, cy;
162
0
    double t1, t2, t3;
163
0
    double sign;
164
0
    double th1, dth;
165
166
0
    gs_currentpoint(ctx->pgs, &pt);
167
0
    x1 = pt.x;
168
0
    y1 = pt.y;
169
0
    x2 = point_x;
170
0
    y2 = point_y;
171
0
    rx = size_x;
172
0
    ry = size_y;
173
174
0
    if (is_clockwise != is_large_arc)
175
0
        sign = 1;
176
0
    else
177
0
        sign = -1;
178
179
0
    gs_make_rotation(rotation_angle, &rotmat);
180
0
    gs_make_rotation(-rotation_angle, &revmat);
181
182
    /* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
183
    /* Conversion from endpoint to center parameterization */
184
185
    /* F.6.6.1 -- ensure radii are positive and non-zero */
186
0
    rx = fabsf(rx);
187
0
    ry = fabsf(ry);
188
0
    if (rx < 0.001 || ry < 0.001 || (x1 == x2 && y1 == y2))
189
0
    {
190
0
        gs_lineto(ctx->pgs, x2, y2);
191
0
        return;
192
0
    }
193
194
    /* F.6.5.1 */
195
0
    gs_distance_transform((x1 - x2) / 2.0, (y1 - y2) / 2.0, &revmat, &pt);
196
0
    x1t = pt.x;
197
0
    y1t = pt.y;
198
199
    /* F.6.6.2 -- ensure radii are large enough */
200
0
    t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
201
0
    if (t1 > 1.0)
202
0
    {
203
0
        rx = rx * sqrtf(t1);
204
0
        ry = ry * sqrtf(t1);
205
0
    }
206
207
    /* F.6.5.2 */
208
0
    t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
209
0
    t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
210
0
    t3 = t1 / t2;
211
    /* guard against rounding errors; sqrt of negative numbers is bad for your health */
212
0
    if (t3 < 0.0) t3 = 0.0;
213
0
    t3 = sqrtf(t3);
214
215
0
    cxt = sign * t3 * (rx * y1t) / ry;
216
0
    cyt = sign * t3 * -(ry * x1t) / rx;
217
218
    /* F.6.5.3 */
219
0
    gs_distance_transform(cxt, cyt, &rotmat, &pt);
220
0
    cx = pt.x + (x1 + x2) / 2;
221
0
    cy = pt.y + (y1 + y2) / 2;
222
223
    /* F.6.5.4 */
224
0
    {
225
0
        gs_point coord1, coord2, coord3, coord4;
226
0
        coord1.x = 1;
227
0
        coord1.y = 0;
228
0
        coord2.x = (x1t - cxt) / rx;
229
0
        coord2.y = (y1t - cyt) / ry;
230
0
        coord3.x = (x1t - cxt) / rx;
231
0
        coord3.y = (y1t - cyt) / ry;
232
0
        coord4.x = (-x1t - cxt) / rx;
233
0
        coord4.y = (-y1t - cyt) / ry;
234
0
        th1 = angle_between(coord1, coord2);
235
0
        dth = angle_between(coord3, coord4);
236
0
        if (dth < 0 && !is_clockwise)
237
0
            dth += (degrees_to_radians * 360);
238
0
        if (dth > 0 && is_clockwise)
239
0
            dth -= (degrees_to_radians * 360);
240
0
    }
241
242
0
    gs_make_identity(&mtx);
243
0
    gs_matrix_translate(&mtx, cx, cy, &mtx);
244
0
    gs_matrix_rotate(&mtx, rotation_angle, &mtx);
245
0
    gs_matrix_scale(&mtx, rx, ry, &mtx);
246
0
    xps_draw_arc_segment(ctx, &mtx, th1, th1 + dth, is_clockwise);
247
248
0
    gs_lineto(ctx->pgs, point_x, point_y);
249
0
}
250
251
/*
252
 * Parse an abbreviated geometry string, and call
253
 * ghostscript moveto/lineto/curveto functions to
254
 * build up a path.
255
 */
256
257
void
258
xps_parse_abbreviated_geometry(xps_context_t *ctx, char *geom)
259
2.85k
{
260
2.85k
    char **args;
261
2.85k
    char **pargs;
262
2.85k
    char *s = geom;
263
2.85k
    gs_point pt;
264
2.85k
    int i, n;
265
2.85k
    int cmd, old;
266
2.85k
    float x1, y1, x2, y2, x3, y3;
267
2.85k
    float smooth_x, smooth_y; /* saved cubic bezier control point for smooth curves */
268
2.85k
    int reset_smooth;
269
270
2.85k
    args = xps_alloc(ctx, (size_t)sizeof(char*) * (strlen(geom) + 1));
271
2.85k
    if (!args) {
272
0
        gs_throw(gs_error_VMerror, "out of memory: args.\n");
273
0
        return;
274
0
    }
275
2.85k
    pargs = args;
276
277
    /*dmprintf1(ctx->memory, "new path (%.70s)\n", geom); */
278
2.85k
    gs_newpath(ctx->pgs);
279
280
116k
    while (*s)
281
113k
    {
282
113k
        if ((*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z'))
283
12.2k
        {
284
12.2k
            *pargs++ = s++;
285
12.2k
        }
286
101k
        else if ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E')
287
46.4k
        {
288
46.4k
            *pargs++ = s;
289
298k
            while ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E')
290
251k
                s ++;
291
46.4k
        }
292
54.5k
        else
293
54.5k
        {
294
54.5k
            s++;
295
54.5k
        }
296
113k
    }
297
298
2.85k
    *pargs = s;
299
300
2.85k
    n = pargs - args;
301
2.85k
    i = 0;
302
303
2.85k
    old = 0;
304
305
2.85k
    reset_smooth = 1;
306
2.85k
    smooth_x = 0.0;
307
2.85k
    smooth_y = 0.0;
308
309
23.4k
    while (i < n)
310
20.5k
    {
311
20.5k
        cmd = args[i][0];
312
20.5k
        if (cmd == '+' || cmd == '.' || cmd == '-' || (cmd >= '0' && cmd <= '9'))
313
8.31k
            cmd = old; /* it's a number, repeat old command */
314
12.2k
        else
315
12.2k
            i ++;
316
317
20.5k
        if (reset_smooth)
318
17.1k
        {
319
17.1k
            smooth_x = 0.0;
320
17.1k
            smooth_y = 0.0;
321
17.1k
        }
322
323
20.5k
        reset_smooth = 1;
324
325
20.5k
        switch (cmd)
326
20.5k
        {
327
2.85k
        case 'F':
328
2.85k
            if (i + 1 <= n)
329
2.85k
            {
330
2.85k
                ctx->fill_rule = atoi(args[i]);
331
2.85k
                i++;
332
2.85k
            }
333
2.85k
            break;
334
335
2.85k
        case 'M':
336
2.85k
            if (i + 2 <= n)
337
2.85k
            {
338
2.85k
                gs_moveto(ctx->pgs, atof(args[i]), atof(args[i+1]));
339
                /*dmprintf2(ctx->memory, "moveto %g %g\n", atof(args[i]), atof(args[i+1])); */
340
2.85k
                i += 2;
341
2.85k
            }
342
2.85k
            break;
343
0
        case 'm':
344
0
            if (i + 2 <= n)
345
0
            {
346
0
                gs_rmoveto(ctx->pgs, atof(args[i]), atof(args[i+1]));
347
                /*dmprintf2(ctx->memory, "rmoveto %g %g\n", atof(args[i]), atof(args[i+1])); */
348
0
                i += 2;
349
0
            }
350
0
            break;
351
352
8.54k
        case 'L':
353
8.54k
            if (i + 2 <= n)
354
8.54k
            {
355
8.54k
                gs_lineto(ctx->pgs, atof(args[i]), atof(args[i+1]));
356
                /*dmprintf2(ctx->memory, "lineto %g %g\n", atof(args[i]), atof(args[i+1])); */
357
8.54k
                i += 2;
358
8.54k
            }
359
8.54k
            break;
360
0
        case 'l':
361
0
            if (i + 2 <= n)
362
0
            {
363
0
                gs_rlineto(ctx->pgs, atof(args[i]), atof(args[i+1]));
364
                /*dmprintf2(ctx->memory, "rlineto %g %g\n", atof(args[i]), atof(args[i+1])); */
365
0
                i += 2;
366
0
            }
367
0
            break;
368
369
0
        case 'H':
370
0
            if (i + 1 <= n)
371
0
            {
372
0
                gs_currentpoint(ctx->pgs, &pt);
373
0
                gs_lineto(ctx->pgs, atof(args[i]), pt.y);
374
                /*dmprintf1(ctx->memory, "hlineto %g\n", atof(args[i])); */
375
0
                i += 1;
376
0
            }
377
0
            break;
378
0
        case 'h':
379
0
            if (i + 1 <= n)
380
0
            {
381
0
                gs_rlineto(ctx->pgs, atof(args[i]), 0.0);
382
                /*dmprintf1(ctx->memory, "rhlineto %g\n", atof(args[i])); */
383
0
                i += 1;
384
0
            }
385
0
            break;
386
387
0
        case 'V':
388
0
            if (i + 1 <= n)
389
0
            {
390
0
                gs_currentpoint(ctx->pgs, &pt);
391
0
                gs_lineto(ctx->pgs, pt.x, atof(args[i]));
392
                /*dmprintf1(ctx->memory, "vlineto %g\n", atof(args[i])); */
393
0
                i += 1;
394
0
            }
395
0
            break;
396
0
        case 'v':
397
0
            if (i + 1 <= n)
398
0
            {
399
0
                gs_rlineto(ctx->pgs, 0.0, atof(args[i]));
400
                /*dmprintf1(ctx->memory, "rvlineto %g\n", atof(args[i])); */
401
0
                i += 1;
402
0
            }
403
0
            break;
404
405
3.46k
        case 'C':
406
3.46k
            if (i + 6 <= n)
407
3.46k
            {
408
3.46k
                x1 = atof(args[i+0]);
409
3.46k
                y1 = atof(args[i+1]);
410
3.46k
                x2 = atof(args[i+2]);
411
3.46k
                y2 = atof(args[i+3]);
412
3.46k
                x3 = atof(args[i+4]);
413
3.46k
                y3 = atof(args[i+5]);
414
3.46k
                gs_curveto(ctx->pgs, x1, y1, x2, y2, x3, y3);
415
3.46k
                i += 6;
416
3.46k
                reset_smooth = 0;
417
3.46k
                smooth_x = x3 - x2;
418
3.46k
                smooth_y = y3 - y2;
419
3.46k
            }
420
3.46k
            break;
421
422
0
        case 'c':
423
0
            if (i + 6 <= n)
424
0
            {
425
0
                gs_currentpoint(ctx->pgs, &pt);
426
0
                x1 = atof(args[i+0]) + pt.x;
427
0
                y1 = atof(args[i+1]) + pt.y;
428
0
                x2 = atof(args[i+2]) + pt.x;
429
0
                y2 = atof(args[i+3]) + pt.y;
430
0
                x3 = atof(args[i+4]) + pt.x;
431
0
                y3 = atof(args[i+5]) + pt.y;
432
0
                gs_curveto(ctx->pgs, x1, y1, x2, y2, x3, y3);
433
0
                i += 6;
434
0
                reset_smooth = 0;
435
0
                smooth_x = x3 - x2;
436
0
                smooth_y = y3 - y2;
437
0
            }
438
0
            break;
439
440
0
        case 'S':
441
0
            if (i + 4 <= n)
442
0
            {
443
0
                gs_currentpoint(ctx->pgs, &pt);
444
0
                x1 = atof(args[i+0]);
445
0
                y1 = atof(args[i+1]);
446
0
                x2 = atof(args[i+2]);
447
0
                y2 = atof(args[i+3]);
448
                /*dmprintf2(ctx->memory, "smooth %g %g\n", smooth_x, smooth_y); */
449
0
                gs_curveto(ctx->pgs, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2);
450
0
                i += 4;
451
0
                reset_smooth = 0;
452
0
                smooth_x = x2 - x1;
453
0
                smooth_y = y2 - y1;
454
0
            }
455
0
            break;
456
457
0
        case 's':
458
0
            if (i + 4 <= n)
459
0
            {
460
0
                gs_currentpoint(ctx->pgs, &pt);
461
0
                x1 = atof(args[i+0]) + pt.x;
462
0
                y1 = atof(args[i+1]) + pt.y;
463
0
                x2 = atof(args[i+2]) + pt.x;
464
0
                y2 = atof(args[i+3]) + pt.y;
465
                /*dmprintf2(ctx->memory, "smooth %g %g\n", smooth_x, smooth_y); */
466
0
                gs_curveto(ctx->pgs, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2);
467
0
                i += 4;
468
0
                reset_smooth = 0;
469
0
                smooth_x = x2 - x1;
470
0
                smooth_y = y2 - y1;
471
0
            }
472
0
            break;
473
474
0
        case 'Q':
475
0
            if (i + 4 <= n)
476
0
            {
477
0
                gs_currentpoint(ctx->pgs, &pt);
478
0
                x1 = atof(args[i+0]);
479
0
                y1 = atof(args[i+1]);
480
0
                x2 = atof(args[i+2]);
481
0
                y2 = atof(args[i+3]);
482
                /*dmprintf4(ctx->memory, "conicto %g %g %g %g\n", x1, y1, x2, y2); */
483
0
                gs_curveto(ctx->pgs,
484
0
                        (pt.x + 2 * x1) / 3, (pt.y + 2 * y1) / 3,
485
0
                        (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
486
0
                        x2, y2);
487
0
                i += 4;
488
0
            }
489
0
            break;
490
0
        case 'q':
491
0
            if (i + 4 <= n)
492
0
            {
493
0
                gs_currentpoint(ctx->pgs, &pt);
494
0
                x1 = atof(args[i+0]) + pt.x;
495
0
                y1 = atof(args[i+1]) + pt.y;
496
0
                x2 = atof(args[i+2]) + pt.x;
497
0
                y2 = atof(args[i+3]) + pt.y;
498
                /*dmprintf4(ctx->memory, "conicto %g %g %g %g\n", x1, y1, x2, y2); */
499
0
                gs_curveto(ctx->pgs,
500
0
                        (pt.x + 2 * x1) / 3, (pt.y + 2 * y1) / 3,
501
0
                        (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
502
0
                        x2, y2);
503
0
                i += 4;
504
0
            }
505
0
            break;
506
507
0
        case 'A':
508
0
            if (i + 7 <= n)
509
0
            {
510
0
                xps_draw_arc(ctx,
511
0
                        atof(args[i+0]), atof(args[i+1]), atof(args[i+2]),
512
0
                        atoi(args[i+3]), atoi(args[i+4]),
513
0
                        atof(args[i+5]), atof(args[i+6]));
514
0
                i += 7;
515
0
            }
516
0
            break;
517
0
        case 'a':
518
0
            if (i + 7 <= n)
519
0
            {
520
0
                gs_currentpoint(ctx->pgs, &pt);
521
0
                xps_draw_arc(ctx,
522
0
                        atof(args[i+0]), atof(args[i+1]), atof(args[i+2]),
523
0
                        atoi(args[i+3]), atoi(args[i+4]),
524
0
                        atof(args[i+5]) + pt.x, atof(args[i+6]) + pt.y);
525
0
                i += 7;
526
0
            }
527
0
            break;
528
529
0
        case 'Z':
530
2.85k
        case 'z':
531
2.85k
            gs_closepath(ctx->pgs);
532
            /*dmputs(ctx->memory, "closepath\n"); */
533
2.85k
            break;
534
535
0
        default:
536
            /* eek */
537
0
            if (old == cmd) /* avoid infinite loop */
538
0
                i++;
539
0
            break;
540
20.5k
        }
541
542
20.5k
        old = cmd;
543
20.5k
    }
544
545
2.85k
    xps_free(ctx, args);
546
2.85k
}
547
548
static void
549
xps_parse_arc_segment(xps_context_t *ctx, xps_item_t *root, int stroking, int *skipped_stroke)
550
0
{
551
    /* ArcSegment pretty much follows the SVG algorithm for converting an
552
     * arc in endpoint representation to an arc in centerpoint
553
     * representation. Once in centerpoint it can be given to the
554
     * graphics library in the form of a postscript arc. */
555
556
0
    float rotation_angle;
557
0
    int is_large_arc, is_clockwise;
558
0
    float point_x, point_y;
559
0
    float size_x, size_y;
560
0
    int is_stroked;
561
562
0
    char *point_att = xps_att(root, "Point");
563
0
    char *size_att = xps_att(root, "Size");
564
0
    char *rotation_angle_att = xps_att(root, "RotationAngle");
565
0
    char *is_large_arc_att = xps_att(root, "IsLargeArc");
566
0
    char *sweep_direction_att = xps_att(root, "SweepDirection");
567
0
    char *is_stroked_att = xps_att(root, "IsStroked");
568
569
0
    if (!point_att || !size_att || !rotation_angle_att || !is_large_arc_att || !sweep_direction_att)
570
0
    {
571
0
        gs_warn("ArcSegment element is missing attributes");
572
0
        return;
573
0
    }
574
575
0
    is_stroked = 1;
576
0
    if (is_stroked_att && !strcmp(is_stroked_att, "false"))
577
0
            is_stroked = 0;
578
0
    if (!is_stroked)
579
0
        *skipped_stroke = 1;
580
581
0
    xps_get_point(point_att, &point_x, &point_y);
582
0
    xps_get_point(size_att, &size_x, &size_y);
583
0
    rotation_angle = atof(rotation_angle_att);
584
0
    is_large_arc = !strcmp(is_large_arc_att, "true");
585
0
    is_clockwise = !strcmp(sweep_direction_att, "Clockwise");
586
587
0
    if (stroking && !is_stroked)
588
0
    {
589
0
        gs_moveto(ctx->pgs, point_x, point_y);
590
0
        return;
591
0
    }
592
593
0
    xps_draw_arc(ctx, size_x, size_y, rotation_angle, is_large_arc, is_clockwise, point_x, point_y);
594
0
}
595
596
static void
597
xps_parse_poly_quadratic_bezier_segment(xps_context_t *ctx, xps_item_t *root, int stroking, int *skipped_stroke)
598
0
{
599
0
    char *points_att = xps_att(root, "Points");
600
0
    char *is_stroked_att = xps_att(root, "IsStroked");
601
0
    float x[2], y[2];
602
0
    int is_stroked;
603
0
    gs_point pt;
604
0
    char *s;
605
0
    int n;
606
607
0
    if (!points_att)
608
0
    {
609
0
        gs_warn("PolyQuadraticBezierSegment element has no points");
610
0
        return;
611
0
    }
612
613
0
    is_stroked = 1;
614
0
    if (is_stroked_att && !strcmp(is_stroked_att, "false"))
615
0
            is_stroked = 0;
616
0
    if (!is_stroked)
617
0
        *skipped_stroke = 1;
618
619
0
    s = points_att;
620
0
    n = 0;
621
0
    while (*s != 0)
622
0
    {
623
0
        while (*s == ' ') s++;
624
0
        s = xps_get_point(s, &x[n], &y[n]);
625
0
        if (s == NULL) {
626
0
            gs_warn("PolyQuadraticBezierSegment element has malformed points");
627
0
            return;
628
0
        }
629
0
        n ++;
630
0
        if (n == 2)
631
0
        {
632
0
            if (stroking && !is_stroked)
633
0
            {
634
0
                gs_moveto(ctx->pgs, x[1], y[1]);
635
0
            }
636
0
            else
637
0
            {
638
0
                gs_currentpoint(ctx->pgs, &pt);
639
0
                gs_curveto(ctx->pgs,
640
0
                        (pt.x + 2 * x[0]) / 3, (pt.y + 2 * y[0]) / 3,
641
0
                        (x[1] + 2 * x[0]) / 3, (y[1] + 2 * y[0]) / 3,
642
0
                        x[1], y[1]);
643
0
            }
644
0
            n = 0;
645
0
        }
646
0
    }
647
0
}
648
649
static void
650
xps_parse_poly_bezier_segment(xps_context_t *ctx, xps_item_t *root, int stroking, int *skipped_stroke)
651
0
{
652
0
    char *points_att = xps_att(root, "Points");
653
0
    char *is_stroked_att = xps_att(root, "IsStroked");
654
0
    float x[3], y[3];
655
0
    int is_stroked;
656
0
    char *s;
657
0
    int n;
658
659
0
    if (!points_att)
660
0
    {
661
0
        gs_warn("PolyBezierSegment element has no points");
662
0
        return;
663
0
    }
664
665
0
    is_stroked = 1;
666
0
    if (is_stroked_att && !strcmp(is_stroked_att, "false"))
667
0
            is_stroked = 0;
668
0
    if (!is_stroked)
669
0
        *skipped_stroke = 1;
670
671
0
    s = points_att;
672
0
    n = 0;
673
0
    while (*s != 0)
674
0
    {
675
0
        while (*s == ' ') s++;
676
0
        s = xps_get_point(s, &x[n], &y[n]);
677
0
        if (s == NULL) {
678
0
            gs_warn("PolyBezierSegment element has malformed points");
679
0
            return;
680
0
        }
681
0
        n ++;
682
0
        if (n == 3)
683
0
        {
684
0
            if (stroking && !is_stroked)
685
0
                gs_moveto(ctx->pgs, x[2], y[2]);
686
0
            else
687
0
                gs_curveto(ctx->pgs, x[0], y[0], x[1], y[1], x[2], y[2]);
688
0
            n = 0;
689
0
        }
690
0
    }
691
0
}
692
693
static void
694
xps_parse_poly_line_segment(xps_context_t *ctx, xps_item_t *root, int stroking, int *skipped_stroke)
695
0
{
696
0
    char *points_att = xps_att(root, "Points");
697
0
    char *is_stroked_att = xps_att(root, "IsStroked");
698
0
    int is_stroked;
699
0
    float xy[2];
700
0
    char *s;
701
702
0
    if (!points_att)
703
0
    {
704
0
        gs_warn("PolyLineSegment element has no points");
705
0
        return;
706
0
    }
707
708
0
    is_stroked = 1;
709
0
    if (is_stroked_att && !strcmp(is_stroked_att, "false"))
710
0
            is_stroked = 0;
711
0
    if (!is_stroked)
712
0
        *skipped_stroke = 1;
713
714
0
    s = points_att;
715
0
    while (*s != 0)
716
0
    {
717
0
        s = xps_get_real_params(s, 2, &xy[0]);
718
0
        if (s == NULL) {
719
0
            gs_warn("PolyLineSegment element has malformed points");
720
0
            return;
721
0
        }
722
0
        if (stroking && !is_stroked)
723
0
            gs_moveto(ctx->pgs, xy[0], xy[1]);
724
0
        else
725
0
            gs_lineto(ctx->pgs, xy[0], xy[1]);
726
0
    }
727
0
}
728
729
static void
730
xps_parse_path_figure(xps_context_t *ctx, xps_item_t *root, int stroking)
731
0
{
732
0
    xps_item_t *node;
733
734
0
    char *is_closed_att;
735
0
    char *start_point_att;
736
0
    char *is_filled_att;
737
738
0
    int is_closed = 0;
739
0
    int is_filled = 1;
740
0
    float start_x = 0.0;
741
0
    float start_y = 0.0;
742
743
0
    int skipped_stroke = 0;
744
745
0
    is_closed_att = xps_att(root, "IsClosed");
746
0
    start_point_att = xps_att(root, "StartPoint");
747
0
    is_filled_att = xps_att(root, "IsFilled");
748
749
0
    if (is_closed_att)
750
0
        is_closed = !strcmp(is_closed_att, "true");
751
0
    if (is_filled_att)
752
0
        is_filled = !strcmp(is_filled_att, "true");
753
0
    if (start_point_att)
754
0
        xps_get_point(start_point_att, &start_x, &start_y);
755
756
0
    if (!stroking && !is_filled) /* not filled, when filling */
757
0
        return;
758
759
0
    gs_moveto(ctx->pgs, start_x, start_y);
760
761
0
    for (node = xps_down(root); node; node = xps_next(node))
762
0
    {
763
0
        if (!strcmp(xps_tag(node), "ArcSegment"))
764
0
            xps_parse_arc_segment(ctx, node, stroking, &skipped_stroke);
765
0
        if (!strcmp(xps_tag(node), "PolyBezierSegment"))
766
0
            xps_parse_poly_bezier_segment(ctx, node, stroking, &skipped_stroke);
767
0
        if (!strcmp(xps_tag(node), "PolyLineSegment"))
768
0
            xps_parse_poly_line_segment(ctx, node, stroking, &skipped_stroke);
769
0
        if (!strcmp(xps_tag(node), "PolyQuadraticBezierSegment"))
770
0
            xps_parse_poly_quadratic_bezier_segment(ctx, node, stroking, &skipped_stroke);
771
0
    }
772
773
0
    if (is_closed)
774
0
    {
775
0
        if (stroking && skipped_stroke)
776
0
            gs_lineto(ctx->pgs, start_x, start_y); /* we've skipped using gs_moveto... */
777
0
        else
778
0
            gs_closepath(ctx->pgs); /* no skipped segments, safe to closepath properly */
779
0
    }
780
0
}
781
782
void
783
xps_parse_path_geometry(xps_context_t *ctx, xps_resource_t *dict, xps_item_t *root, int stroking)
784
0
{
785
0
    xps_item_t *node;
786
787
0
    char *figures_att;
788
0
    char *fill_rule_att;
789
0
    char *transform_att;
790
791
0
    xps_item_t *transform_tag = NULL;
792
0
    xps_item_t *figures_tag = NULL; /* only used by resource */
793
794
0
    gs_matrix transform;
795
0
    gs_matrix saved_transform;
796
797
0
    gs_newpath(ctx->pgs);
798
799
0
    figures_att = xps_att(root, "Figures");
800
0
    fill_rule_att = xps_att(root, "FillRule");
801
0
    transform_att = xps_att(root, "Transform");
802
803
0
    for (node = xps_down(root); node; node = xps_next(node))
804
0
    {
805
0
        if (!strcmp(xps_tag(node), "PathGeometry.Transform"))
806
0
            transform_tag = xps_down(node);
807
0
    }
808
809
0
    xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
810
0
    xps_resolve_resource_reference(ctx, dict, &figures_att, &figures_tag, NULL);
811
812
0
    if (fill_rule_att)
813
0
    {
814
0
        if (!strcmp(fill_rule_att, "NonZero"))
815
0
            ctx->fill_rule = 1;
816
0
        if (!strcmp(fill_rule_att, "EvenOdd"))
817
0
            ctx->fill_rule = 0;
818
0
    }
819
820
0
    gs_make_identity(&transform);
821
0
    if (transform_att || transform_tag)
822
0
    {
823
0
        if (transform_att)
824
0
            xps_parse_render_transform(ctx, transform_att, &transform);
825
0
        if (transform_tag)
826
0
            xps_parse_matrix_transform(ctx, transform_tag, &transform);
827
0
    }
828
829
0
    gs_currentmatrix(ctx->pgs, &saved_transform);
830
0
    gs_concat(ctx->pgs, &transform);
831
832
0
    if (figures_att)
833
0
    {
834
0
        xps_parse_abbreviated_geometry(ctx, figures_att);
835
0
    }
836
837
0
    if (figures_tag)
838
0
    {
839
0
        xps_parse_path_figure(ctx, figures_tag, stroking);
840
0
    }
841
842
0
    for (node = xps_down(root); node; node = xps_next(node))
843
0
    {
844
0
        if (!strcmp(xps_tag(node), "PathFigure"))
845
0
            xps_parse_path_figure(ctx, node, stroking);
846
0
    }
847
848
0
    gs_setmatrix(ctx->pgs, &saved_transform);
849
0
}
850
851
static int
852
xps_parse_line_cap(char *attr)
853
8.55k
{
854
8.55k
    if (attr)
855
68
    {
856
68
        if (!strcmp(attr, "Flat")) return gs_cap_butt;
857
68
        if (!strcmp(attr, "Square")) return gs_cap_square;
858
68
        if (!strcmp(attr, "Round")) return gs_cap_round;
859
0
        if (!strcmp(attr, "Triangle")) return gs_cap_triangle;
860
0
    }
861
8.49k
    return gs_cap_butt;
862
8.55k
}
863
864
static void
865
pdfmark_bbox_transform(gs_rect *bbox, gs_matrix *matrix)
866
0
{
867
0
    gs_point aa, az, za, zz;
868
0
    double temp;
869
0
    gs_matrix matrix2;
870
871
0
    if (gs_matrix_invert(matrix, &matrix2) < 0)
872
0
        return;
873
874
0
    gs_point_transform(bbox->p.x, bbox->p.y, &matrix2, &aa);
875
0
    gs_point_transform(bbox->p.x, bbox->q.y, &matrix2, &az);
876
0
    gs_point_transform(bbox->q.x, bbox->p.y, &matrix2, &za);
877
0
    gs_point_transform(bbox->q.x, bbox->q.y, &matrix2, &zz);
878
879
0
    if ( aa.x > az.x)
880
0
        temp = aa.x, aa.x = az.x, az.x = temp;
881
0
    if ( za.x > zz.x)
882
0
        temp = za.x, za.x = zz.x, zz.x = temp;
883
0
    if ( za.x < aa.x)
884
0
        aa.x = za.x;  /* min */
885
0
    if ( az.x > zz.x)
886
0
        zz.x = az.x;  /* max */
887
888
0
    if ( aa.y > az.y)
889
0
        temp = aa.y, aa.y = az.y, az.y = temp;
890
0
    if ( za.y > zz.y)
891
0
        temp = za.y, za.y = zz.y, zz.y = temp;
892
0
    if ( za.y < aa.y)
893
0
        aa.y = za.y;  /* min */
894
0
    if ( az.y > zz.y)
895
0
        zz.y = az.y;  /* max */
896
897
0
    bbox->p.x = aa.x;
898
0
    bbox->p.y = aa.y;
899
0
    bbox->q.x = zz.x;
900
0
    bbox->q.y = zz.y;
901
0
}
902
903
static int check_pdfmark(xps_context_t *ctx, gx_device *dev)
904
0
{
905
0
    gs_c_param_list list;
906
0
    int code = -1;
907
0
    dev_param_req_t request;
908
0
    char pdfmark[] = "pdfmark";
909
910
    /* Check if the device supports pdfmark (pdfwrite) */
911
0
    gs_c_param_list_write(&list, ctx->pgs->device->memory);
912
0
    request.Param = pdfmark;
913
0
    request.list = &list;
914
0
    code = dev_proc(dev, dev_spec_op)(dev, gxdso_get_dev_param, &request, sizeof(dev_param_req_t));
915
0
    gs_c_param_list_release(&list);
916
0
    return code;
917
0
}
918
919
static int pdfmark_write_param_list_array(xps_context_t *ctx, const gs_param_string_array *array_list)
920
0
{
921
0
    gs_c_param_list list;
922
0
    int code = 0;
923
924
    /* Set the list to writeable, and initialise it */
925
0
    gs_c_param_list_write(&list, ctx->memory);
926
    /* We don't want keys to be persistent, as we are going to throw
927
     * away our array, force them to be copied
928
     */
929
0
    gs_param_list_set_persistent_keys((gs_param_list *) &list, false);
930
931
    /* Make really sure the list is writable, but don't initialise it */
932
0
    gs_c_param_list_write_more(&list);
933
934
    /* Add the param string array to the list */
935
0
    code = param_write_string_array((gs_param_list *)&list, "pdfmark", (const gs_param_string_array *)array_list);
936
0
    if (code < 0)
937
0
        return code;
938
939
    /* Set the param list back to readable, so putceviceparams can readit (mad...) */
940
0
    gs_c_param_list_read(&list);
941
942
    /* and set the actual device parameters */
943
0
    code = gs_putdeviceparams(ctx->pgs->device, (gs_param_list *)&list);
944
945
0
    gs_c_param_list_release(&list);
946
0
    return code;
947
0
}
948
949
static int pdfmark_link(xps_context_t *ctx, char *navigate_uri_att, gs_rect *path_bbox, float *samples)
950
0
{
951
0
    gx_device *dev = ctx->pgs->device;
952
0
    int code = 0;
953
954
0
    code = check_pdfmark(ctx, dev);
955
956
0
    if (code >= 0) {
957
0
        gs_matrix ctm_placeholder;
958
0
        gs_param_string_array array_list;
959
0
        gs_param_string *parray = NULL;
960
0
        char ctmstr[256];
961
0
        char objdef0[] = "/_objdef", objdef1[256], objdef2[] = "/type", objdef3[] = "/dict", objdef4[] = "OBJ";
962
0
        char uridef0[] = "/S", uridef1[] = "/URI", uridef2[256], uridef3[] = ".PUTDICT";
963
0
        char linkdef0[] = "/A", linkdef1[] = "/Rect", linkdef2[] = "/Subtype", linkdef3[] = "/Link", linkdef4[] = "LNK", linkRect[256];
964
0
        char colordef0[] = "/C", colordef1[256];
965
966
0
        parray = (gs_param_string *)gs_alloc_bytes(ctx->memory, 10*sizeof(gs_param_string),
967
0
                                                   "pdfi_pdfmark_from_dict(parray)");
968
0
        if (parray == NULL) {
969
0
            code = gs_note_error(gs_error_VMerror);
970
0
            return code;
971
0
        }
972
973
0
        gs_currentmatrix(ctx->pgs, &ctm_placeholder);
974
0
        gs_snprintf(ctmstr, 256, "[%.4f %.4f %.4f %.4f %.4f %.4f]", ctm_placeholder.xx, ctm_placeholder.xy, ctm_placeholder.yx, ctm_placeholder.yy, ctm_placeholder.tx, ctm_placeholder.ty);
975
976
0
        memset(parray, 0, 10*sizeof(gs_param_string));
977
0
        gs_snprintf(objdef1, 256, "{Obj%dG0}", gs_next_ids(ctx->pgs->device->memory, 1));
978
0
        parray[0].data = (const byte *)objdef0;
979
0
        parray[0].size = strlen(objdef0);
980
0
        parray[1].data = (const byte *)objdef1;
981
0
        parray[1].size = strlen(objdef1);
982
0
        parray[2].data = (const byte *)objdef2;
983
0
        parray[2].size = strlen(objdef2);
984
0
        parray[3].data = (const byte *)objdef3;
985
0
        parray[3].size = strlen(objdef3);
986
0
        parray[4].data = (const byte *)ctmstr;
987
0
        parray[4].size = strlen(ctmstr);
988
0
        parray[5].data = (const byte *)objdef4;
989
0
        parray[5].size = strlen(objdef4);
990
991
0
        array_list.data = parray;
992
0
        array_list.persistent = false;
993
0
        array_list.size = 6;
994
995
0
        code = pdfmark_write_param_list_array(ctx, (const gs_param_string_array *)&array_list);
996
0
        if (code < 0)
997
0
            goto  exit1;
998
999
0
        gs_snprintf(uridef2, 256, "(%s)", navigate_uri_att);
1000
0
        memset(parray, 0, 10*sizeof(gs_param_string));
1001
0
        parray[0].data = (const byte *)objdef1;
1002
0
        parray[0].size = strlen(objdef1);
1003
0
        parray[1].data = (const byte *)uridef0;
1004
0
        parray[1].size = strlen(uridef0);
1005
0
        parray[2].data = (const byte *)uridef1;
1006
0
        parray[2].size = strlen(uridef1);
1007
0
        parray[3].data = (const byte *)uridef1;
1008
0
        parray[3].size = strlen(uridef1);
1009
0
        parray[4].data = (const byte *)uridef2;
1010
0
        parray[4].size = strlen(uridef2);
1011
0
        parray[5].data = (const byte *)ctmstr;
1012
0
        parray[5].size = strlen(ctmstr);
1013
0
        parray[6].data = (const byte *)uridef3;
1014
0
        parray[6].size = strlen(uridef3);
1015
1016
0
        array_list.data = parray;
1017
0
        array_list.persistent = false;
1018
0
        array_list.size = 7;
1019
1020
0
        code = pdfmark_write_param_list_array(ctx, (const gs_param_string_array *)&array_list);
1021
0
        if (code < 0)
1022
0
            goto  exit1;
1023
1024
0
        memset(parray, 0, 10*sizeof(gs_param_string));
1025
1026
0
        pdfmark_bbox_transform(path_bbox, &ctm_placeholder);
1027
0
        gs_snprintf(linkRect, 256, "[%f %f %f %f]", path_bbox->p.x, path_bbox->p.y, path_bbox->q.x, path_bbox->q.y);
1028
0
        if (samples[3] == 0x00)
1029
0
            gs_snprintf(colordef1, 256, "[]");
1030
0
        else
1031
0
            gs_snprintf(colordef1, 256, "[%.4f %.4f %.4f]", samples[0], samples[1], samples[2]);
1032
0
        parray[0].data = (const byte *)linkdef0;
1033
0
        parray[0].size = strlen(linkdef0);
1034
0
        parray[1].data = (const byte *)objdef1;
1035
0
        parray[1].size = strlen(objdef1);
1036
0
        parray[2].data = (const byte *)linkdef1;
1037
0
        parray[2].size = strlen(linkdef1);
1038
0
        parray[3].data = (const byte *)linkRect;
1039
0
        parray[3].size = strlen(linkRect);
1040
0
        parray[4].data = (const byte *)colordef0;
1041
0
        parray[4].size = strlen(colordef0);
1042
0
        parray[5].data = (const byte *)colordef1;
1043
0
        parray[5].size = strlen(colordef1);
1044
0
        parray[6].data = (const byte *)linkdef2;
1045
0
        parray[6].size = strlen(linkdef2);
1046
0
        parray[7].data = (const byte *)linkdef3;
1047
0
        parray[7].size = strlen(linkdef3);
1048
0
        parray[8].data = (const byte *)ctmstr;
1049
0
        parray[8].size = strlen(ctmstr);
1050
0
        parray[9].data = (const byte *)linkdef4;
1051
0
        parray[9].size = strlen(linkdef4);
1052
1053
0
        array_list.data = parray;
1054
0
        array_list.persistent = false;
1055
0
        array_list.size = 10;
1056
1057
0
        code = pdfmark_write_param_list_array(ctx, (const gs_param_string_array *)&array_list);
1058
1059
0
exit1:
1060
0
        gs_free_object(ctx->memory, parray, "pdfi_pdfmark_from_dict(parray)");
1061
0
    } else
1062
0
        code = 0;
1063
1064
0
    return code;
1065
0
}
1066
1067
/*
1068
 * Parse an XPS <Path> element, and call relevant ghostscript
1069
 * functions for drawing and/or clipping the child elements.
1070
 */
1071
1072
int
1073
xps_parse_path(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
1074
2.85k
{
1075
2.85k
    xps_item_t *node;
1076
2.85k
    int code;
1077
1078
2.85k
    char *fill_uri;
1079
2.85k
    char *stroke_uri;
1080
2.85k
    char *opacity_mask_uri;
1081
1082
2.85k
    char *transform_att;
1083
2.85k
    char *clip_att;
1084
2.85k
    char *data_att;
1085
2.85k
    char *fill_att;
1086
2.85k
    char *stroke_att;
1087
2.85k
    char *opacity_att;
1088
2.85k
    char *opacity_mask_att;
1089
2.85k
    char *navigate_uri_att;
1090
1091
2.85k
    xps_item_t *transform_tag = NULL;
1092
2.85k
    xps_item_t *clip_tag = NULL;
1093
2.85k
    xps_item_t *data_tag = NULL;
1094
2.85k
    xps_item_t *fill_tag = NULL;
1095
2.85k
    xps_item_t *stroke_tag = NULL;
1096
2.85k
    xps_item_t *opacity_mask_tag = NULL;
1097
1098
2.85k
    char *fill_opacity_att = NULL;
1099
2.85k
    char *stroke_opacity_att = NULL;
1100
1101
2.85k
    char *stroke_dash_array_att;
1102
2.85k
    char *stroke_dash_cap_att;
1103
2.85k
    char *stroke_dash_offset_att;
1104
2.85k
    char *stroke_end_line_cap_att;
1105
2.85k
    char *stroke_start_line_cap_att;
1106
2.85k
    char *stroke_line_join_att;
1107
2.85k
    char *stroke_miter_limit_att;
1108
2.85k
    char *stroke_thickness_att;
1109
1110
2.85k
    gs_line_join linejoin;
1111
2.85k
    float linewidth;
1112
2.85k
    float miterlimit;
1113
2.85k
    float samples[XPS_MAX_COLORS] = {0, 0, 0, 0};
1114
1115
2.85k
    bool opacity_pushed = false;
1116
2.85k
    bool uses_stroke = false;
1117
1118
2.85k
    gs_rect path_bbox = {{0.0, 0.0}, {0.0, 0.0}};
1119
1120
2.85k
    gs_gsave(ctx->pgs);
1121
1122
2.85k
    ctx->fill_rule = 0;
1123
1124
    /*
1125
     * Extract attributes and extended attributes.
1126
     */
1127
1128
2.85k
    transform_att = xps_att(root, "RenderTransform");
1129
2.85k
    clip_att = xps_att(root, "Clip");
1130
2.85k
    data_att = xps_att(root, "Data");
1131
2.85k
    fill_att = xps_att(root, "Fill");
1132
2.85k
    stroke_att = xps_att(root, "Stroke");
1133
2.85k
    opacity_att = xps_att(root, "Opacity");
1134
2.85k
    opacity_mask_att = xps_att(root, "OpacityMask");
1135
2.85k
    navigate_uri_att = xps_att(root, "FixedPage.NavigateUri");
1136
1137
2.85k
    stroke_dash_array_att = xps_att(root, "StrokeDashArray");
1138
2.85k
    stroke_dash_cap_att = xps_att(root, "StrokeDashCap");
1139
2.85k
    stroke_dash_offset_att = xps_att(root, "StrokeDashOffset");
1140
2.85k
    stroke_end_line_cap_att = xps_att(root, "StrokeEndLineCap");
1141
2.85k
    stroke_start_line_cap_att = xps_att(root, "StrokeStartLineCap");
1142
2.85k
    stroke_line_join_att = xps_att(root, "StrokeLineJoin");
1143
2.85k
    stroke_miter_limit_att = xps_att(root, "StrokeMiterLimit");
1144
2.85k
    stroke_thickness_att = xps_att(root, "StrokeThickness");
1145
1146
2.85k
    for (node = xps_down(root); node; node = xps_next(node))
1147
0
    {
1148
0
        if (!strcmp(xps_tag(node), "Path.RenderTransform"))
1149
0
            transform_tag = xps_down(node);
1150
1151
0
        if (!strcmp(xps_tag(node), "Path.OpacityMask"))
1152
0
            opacity_mask_tag = xps_down(node);
1153
1154
0
        if (!strcmp(xps_tag(node), "Path.Clip"))
1155
0
            clip_tag = xps_down(node);
1156
1157
0
        if (!strcmp(xps_tag(node), "Path.Fill"))
1158
0
            fill_tag = xps_down(node);
1159
1160
0
        if (!strcmp(xps_tag(node), "Path.Stroke"))
1161
0
            stroke_tag = xps_down(node);
1162
1163
0
        if (!strcmp(xps_tag(node), "Path.Data"))
1164
0
            data_tag = xps_down(node);
1165
0
    }
1166
1167
2.85k
    fill_uri = base_uri;
1168
2.85k
    stroke_uri = base_uri;
1169
2.85k
    opacity_mask_uri = base_uri;
1170
1171
2.85k
    xps_resolve_resource_reference(ctx, dict, &data_att, &data_tag, NULL);
1172
2.85k
    xps_resolve_resource_reference(ctx, dict, &clip_att, &clip_tag, NULL);
1173
2.85k
    xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
1174
2.85k
    xps_resolve_resource_reference(ctx, dict, &fill_att, &fill_tag, &fill_uri);
1175
2.85k
    xps_resolve_resource_reference(ctx, dict, &stroke_att, &stroke_tag, &stroke_uri);
1176
2.85k
    xps_resolve_resource_reference(ctx, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
1177
1178
    /*
1179
     * Act on the information we have gathered:
1180
     */
1181
1182
2.85k
    if (fill_tag && !strcmp(xps_tag(fill_tag), "SolidColorBrush"))
1183
0
    {
1184
0
        fill_opacity_att = xps_att(fill_tag, "Opacity");
1185
0
        fill_att = xps_att(fill_tag, "Color");
1186
0
        fill_tag = NULL;
1187
0
    }
1188
1189
2.85k
    if (stroke_tag && !strcmp(xps_tag(stroke_tag), "SolidColorBrush"))
1190
0
    {
1191
0
        stroke_opacity_att = xps_att(stroke_tag, "Opacity");
1192
0
        stroke_att = xps_att(stroke_tag, "Color");
1193
0
        stroke_tag = NULL;
1194
0
    }
1195
1196
2.85k
    gs_setlinestartcap(ctx->pgs, xps_parse_line_cap(stroke_start_line_cap_att));
1197
2.85k
    gs_setlineendcap(ctx->pgs, xps_parse_line_cap(stroke_end_line_cap_att));
1198
2.85k
    gs_setlinedashcap(ctx->pgs, xps_parse_line_cap(stroke_dash_cap_att));
1199
1200
2.85k
    linejoin = gs_join_miter;
1201
2.85k
    if (stroke_line_join_att)
1202
34
    {
1203
34
        if (!strcmp(stroke_line_join_att, "Miter")) linejoin = gs_join_miter;
1204
34
        if (!strcmp(stroke_line_join_att, "Bevel")) linejoin = gs_join_bevel;
1205
34
        if (!strcmp(stroke_line_join_att, "Round")) linejoin = gs_join_round;
1206
34
    }
1207
2.85k
    gs_setlinejoin(ctx->pgs, linejoin);
1208
1209
2.85k
    miterlimit = 10.0;
1210
2.85k
    if (stroke_miter_limit_att)
1211
0
        miterlimit = atof(stroke_miter_limit_att);
1212
2.85k
    gs_setmiterlimit(ctx->pgs, miterlimit);
1213
1214
2.85k
    linewidth = 1.0;
1215
2.85k
    if (stroke_thickness_att)
1216
62
        linewidth = atof(stroke_thickness_att);
1217
2.85k
    gs_setlinewidth(ctx->pgs, linewidth);
1218
1219
2.85k
    if (stroke_dash_array_att)
1220
0
    {
1221
0
        char *s = stroke_dash_array_att;
1222
0
        float *dash_array;
1223
0
        float dash_offset = 0.0;
1224
0
        int dash_count = 0;
1225
0
        int dash_mem_count = 0;
1226
1227
        /* Do an initial reasonable allocation. If that
1228
           runs out, double until we get to a max size
1229
           and then just add that max each overrun. */
1230
0
        dash_array = xps_alloc(ctx, sizeof(float) * INITIAL_DASH_SIZE);
1231
0
        if (dash_array == NULL)
1232
0
        {
1233
0
            gs_throw(gs_error_VMerror, "out of memory: dash_array.\n");
1234
0
            return gs_error_VMerror;
1235
0
        }
1236
0
        dash_mem_count = INITIAL_DASH_SIZE;
1237
1238
0
        if (stroke_dash_offset_att)
1239
0
            dash_offset = atof(stroke_dash_offset_att) * linewidth;
1240
1241
0
        while (*s)
1242
0
        {
1243
0
            while (*s == ' ')
1244
0
                s++;
1245
0
            if (*s) /* needed in case of a space before the last quote */
1246
0
            {
1247
                /* Double up to a max size of ADDITIVE_DASH_SIZE and then add
1248
                   that amount each time */
1249
0
                if (dash_count > (dash_mem_count - 1))
1250
0
                {
1251
0
                    if (dash_mem_count < ADDITIVE_DASH_SIZE)
1252
0
                        dash_mem_count = dash_mem_count * 2;
1253
0
                    else
1254
0
                        dash_mem_count = dash_mem_count + ADDITIVE_DASH_SIZE;
1255
0
                    dash_array = (float*) xps_realloc(ctx, dash_array, (size_t)sizeof(float) * dash_mem_count);
1256
0
                    if (dash_array == NULL)
1257
0
                    {
1258
0
                        gs_throw(gs_error_VMerror, "out of memory: dash_array realloc.\n");
1259
0
                        return gs_error_VMerror;
1260
0
                    }
1261
0
                }
1262
0
                dash_array[dash_count++] = atof(s) * linewidth;
1263
0
            }
1264
0
            while (*s && *s != ' ')
1265
0
                s++;
1266
0
        }
1267
1268
0
        if (dash_count > 0)
1269
0
        {
1270
0
            float phase_len = 0;
1271
0
            int i;
1272
0
            for (i = 0; i < dash_count; ++i)
1273
0
                phase_len += dash_array[i];
1274
0
            if (phase_len == 0)
1275
0
                dash_count = 0;
1276
0
        }
1277
0
        gs_setdash(ctx->pgs, dash_array, dash_count, dash_offset);
1278
0
        xps_free(ctx, dash_array);
1279
0
    }
1280
2.85k
    else
1281
2.85k
    {
1282
2.85k
        gs_setdash(ctx->pgs, NULL, 0, 0.0);
1283
2.85k
    }
1284
1285
2.85k
    if (transform_att || transform_tag)
1286
0
    {
1287
0
        gs_matrix transform;
1288
1289
0
        if (transform_att)
1290
0
            xps_parse_render_transform(ctx, transform_att, &transform);
1291
0
        if (transform_tag)
1292
0
            xps_parse_matrix_transform(ctx, transform_tag, &transform);
1293
1294
0
        gs_concat(ctx->pgs, &transform);
1295
0
    }
1296
1297
2.85k
    if (clip_att || clip_tag)
1298
0
    {
1299
0
        if (clip_att)
1300
0
            xps_parse_abbreviated_geometry(ctx, clip_att);
1301
0
        if (clip_tag)
1302
0
            xps_parse_path_geometry(ctx, dict, clip_tag, 0);
1303
0
        xps_clip(ctx);
1304
0
    }
1305
1306
#if 0 /* XXX */
1307
    if (opacity_att || opacity_mask_tag)
1308
    {
1309
        /* clip the bounds with the actual path */
1310
        if (data_att)
1311
            xps_parse_abbreviated_geometry(ctx, data_att);
1312
        if (data_tag)
1313
            xps_parse_path_geometry(ctx, dict, data_tag, 0);
1314
        xps_update_bounds(ctx, &saved_bounds_opacity);
1315
        gs_newpath(ctx->pgs);
1316
    }
1317
#endif
1318
    /* xps_begin_opacity put into the fill_att, etc loops so that we can
1319
       push groups of smaller sizes.  This makes it necessary to add a pushed
1320
       flag so that we don't do multiple pushes if we have a fill and stroke
1321
       attribute for the same group. */
1322
2.85k
    if (stroke_att || stroke_tag) {
1323
62
        uses_stroke = true;
1324
62
    }
1325
2.85k
    if (fill_att)
1326
2.79k
    {
1327
2.79k
        gs_color_space *colorspace;
1328
1329
2.79k
        if (data_att)
1330
2.79k
            xps_parse_abbreviated_geometry(ctx, data_att);
1331
2.79k
        if (data_tag)
1332
0
            xps_parse_path_geometry(ctx, dict, data_tag, 0);
1333
1334
2.79k
        if (navigate_uri_att) {
1335
0
            code = gx_curr_bbox(ctx->pgs, &path_bbox, PATH_FILL);
1336
0
            if (code < 0)
1337
0
                navigate_uri_att = NULL;
1338
0
        }
1339
1340
2.79k
        code = xps_begin_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag, true, uses_stroke);
1341
2.79k
        if (code)
1342
0
        {
1343
0
            gs_grestore(ctx->pgs);
1344
0
            return gs_rethrow(code, "cannot create transparency group");
1345
0
        }
1346
1347
        /* Color must be set *after* we begin opacity */
1348
2.79k
        xps_parse_color(ctx, base_uri, fill_att, &colorspace, samples);
1349
2.79k
        if (fill_opacity_att)
1350
0
            samples[0] *= atof(fill_opacity_att);
1351
2.79k
        xps_set_color(ctx, colorspace, samples);
1352
2.79k
        rc_decrement(colorspace, "xps_parse_path");
1353
1354
2.79k
        opacity_pushed = true;
1355
2.79k
        xps_fill(ctx);
1356
2.79k
    }
1357
1358
2.85k
    if (fill_tag)
1359
0
    {
1360
0
        if (data_att)
1361
0
            xps_parse_abbreviated_geometry(ctx, data_att);
1362
0
        if (data_tag)
1363
0
            xps_parse_path_geometry(ctx, dict, data_tag, 0);
1364
1365
0
        if (!opacity_pushed) {
1366
0
            code = xps_begin_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag, true, uses_stroke);
1367
0
            if (code)
1368
0
            {
1369
0
                gs_grestore(ctx->pgs);
1370
0
                return gs_rethrow(code, "cannot create transparency group");
1371
0
            }
1372
0
            opacity_pushed = true;
1373
0
        }
1374
1375
0
        code = xps_parse_brush(ctx, fill_uri, dict, fill_tag);
1376
0
        if (code < 0)
1377
0
        {
1378
0
            xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
1379
0
            gs_grestore(ctx->pgs);
1380
0
            return gs_rethrow(code, "cannot parse fill brush");
1381
0
        }
1382
0
    }
1383
1384
2.85k
    if (stroke_att)
1385
62
    {
1386
62
        gs_color_space *colorspace;
1387
1388
62
        if (data_att)
1389
62
            xps_parse_abbreviated_geometry(ctx, data_att);
1390
62
        if (data_tag)
1391
0
            xps_parse_path_geometry(ctx, dict, data_tag, 1);
1392
1393
62
        if (!opacity_pushed) {
1394
62
            code = xps_begin_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag, true, true);
1395
62
            if (code)
1396
0
            {
1397
0
                gs_grestore(ctx->pgs);
1398
0
                return gs_rethrow(code, "cannot create transparency group");
1399
0
            }
1400
62
            opacity_pushed = true;
1401
62
        }
1402
1403
        /* Color must be set *after* the group is pushed */
1404
62
        xps_parse_color(ctx, base_uri, stroke_att, &colorspace, samples);
1405
62
        if (stroke_opacity_att)
1406
0
            samples[0] *= atof(stroke_opacity_att);
1407
62
        xps_set_color(ctx, colorspace, samples);
1408
62
        rc_decrement(colorspace, "xps_parse_path");
1409
1410
62
        gs_stroke(ctx->pgs);
1411
62
    }
1412
1413
2.85k
    if (stroke_tag)
1414
0
    {
1415
0
        if (data_att)
1416
0
            xps_parse_abbreviated_geometry(ctx, data_att);
1417
0
        if (data_tag)
1418
0
            xps_parse_path_geometry(ctx, dict, data_tag, 1);
1419
1420
0
        if (navigate_uri_att) {
1421
0
            code = gx_curr_bbox(ctx->pgs, &path_bbox, PATH_FILL);
1422
0
            if (code < 0)
1423
0
                navigate_uri_att = NULL;
1424
0
        }
1425
1426
0
        if (!opacity_pushed) {
1427
0
            code = xps_begin_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag, true, true);
1428
0
            if (code)
1429
0
            {
1430
0
                gs_grestore(ctx->pgs);
1431
0
                return gs_rethrow(code, "cannot create transparency group");
1432
0
            }
1433
0
            opacity_pushed = true;
1434
0
        }
1435
1436
0
        ctx->fill_rule = 1; /* over-ride fill rule when converting outline to stroked */
1437
0
        gs_strokepath2(ctx->pgs);
1438
1439
0
        code = xps_parse_brush(ctx, stroke_uri, dict, stroke_tag);
1440
0
        if (code < 0)
1441
0
        {
1442
0
            xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
1443
0
            gs_grestore(ctx->pgs);
1444
0
            return gs_rethrow(code, "cannot parse stroke brush");
1445
0
        }
1446
0
    }
1447
1448
2.85k
    xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
1449
1450
2.85k
    if (navigate_uri_att)
1451
0
        (void)pdfmark_link(ctx, navigate_uri_att, &path_bbox, samples);
1452
1453
2.85k
    gs_grestore(ctx->pgs);
1454
2.85k
    return 0;
1455
2.85k
}