Coverage Report

Created: 2026-07-24 07:44

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