Coverage Report

Created: 2026-10-28 08:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/xps/xpsgradient.c
Line
Count
Source
1
/* Copyright (C) 2001-2026 Artifex Software, Inc.
2
   All Rights Reserved.
3
4
   This software is provided AS-IS with no warranty, either express or
5
   implied.
6
7
   This software is distributed under license and may not be copied,
8
   modified or distributed except as expressly authorized under the terms
9
   of the license contained in the file LICENSE in this distribution.
10
11
   Refer to licensing information at http://www.artifex.com or contact
12
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
13
   CA 94129, USA, for further information.
14
*/
15
16
17
/* XPS interpreter - gradient support */
18
19
#include "ghostxps.h"
20
21
22
#define MAX_STOPS 256
22
23
enum { SPREAD_PAD, SPREAD_REPEAT, SPREAD_REFLECT };
24
25
/*
26
 * Parse a list of GradientStop elements.
27
 * Fill the offset and color arrays, and
28
 * return the number of stops parsed.
29
 */
30
31
struct stop
32
{
33
    float offset;
34
    float color[4];
35
    int index;
36
};
37
38
static int cmp_stop(const void *a, const void *b)
39
33
{
40
33
    const struct stop *astop = a;
41
33
    const struct stop *bstop = b;
42
33
    float diff = astop->offset - bstop->offset;
43
33
    if (diff < 0)
44
33
        return -1;
45
0
    if (diff > 0)
46
0
        return 1;
47
0
    return astop->index - bstop->index;
48
0
}
49
50
static inline float lerp(float a, float b, float x)
51
0
{
52
0
    return a + (b - a) * x;
53
0
}
54
55
static int
56
xps_parse_gradient_stops(xps_context_t *ctx, char *base_uri, xps_item_t *node,
57
    struct stop *stops, int maxcount)
58
22
{
59
22
    unsigned short sample_in[XPS_MAX_COLORS], sample_out[XPS_MAX_COLORS]; /* XPS allows up to 8 bands */
60
22
    gsicc_rendering_param_t rendering_params;
61
22
    gsicc_link_t *icclink = 0;
62
22
    float sample[XPS_MAX_COLORS];
63
22
    int before, after;
64
22
    int count;
65
22
    int i, k;
66
67
    /* We may have to insert 2 extra stops when postprocessing */
68
22
    maxcount -= 2;
69
70
22
    count = 0;
71
77
    while (node && count < maxcount)
72
55
    {
73
55
        if (!strcmp(xps_tag(node), "GradientStop"))
74
55
        {
75
55
            char *offset = xps_att(node, "Offset");
76
55
            char *color = xps_att(node, "Color");
77
55
            if (offset && color)
78
55
            {
79
55
                gs_color_space *colorspace;
80
81
55
                stops[count].offset = atof(offset);
82
55
                stops[count].index = count;
83
84
55
                xps_parse_color(ctx, base_uri, color, &colorspace, sample);
85
55
                if (colorspace == NULL)
86
0
                    return 0;
87
88
                /* Set the rendering parameters */
89
55
                rendering_params.black_point_comp = gsBLACKPTCOMP_ON;
90
55
                rendering_params.graphics_type_tag = GS_VECTOR_TAG;
91
55
                rendering_params.override_icc = false;
92
55
                rendering_params.preserve_black = gsBKPRESNOTSPECIFIED;
93
55
                rendering_params.rendering_intent = gsPERCEPTUAL;
94
55
                rendering_params.cmm = gsCMM_DEFAULT;
95
                /* Get link to map from source to sRGB */
96
55
                icclink = gsicc_get_link(ctx->pgs, NULL, colorspace,
97
55
                                ctx->srgb, &rendering_params, ctx->memory);
98
99
55
                if (icclink != NULL && !icclink->is_identity)
100
0
                {
101
                    /* Transform the color */
102
0
                    int num_colors = gsicc_getsrc_channel_count(colorspace->cmm_icc_profile_data);
103
0
                    for (i = 0; i < num_colors; i++)
104
0
                    {
105
0
                        sample_in[i] = (unsigned short)(sample[i+1]*65535);
106
0
                    }
107
0
                    gscms_transform_color((gx_device *)(ctx->pgs->device),
108
0
                                          icclink, sample_in, sample_out, 2);
109
110
0
                    stops[count].color[0] = sample[0]; /* Alpha */
111
0
                    stops[count].color[1] = (float) sample_out[0] / 65535.0; /* sRGB */
112
0
                    stops[count].color[2] = (float) sample_out[1] / 65535.0;
113
0
                    stops[count].color[3] = (float) sample_out[2] / 65535.0;
114
0
                }
115
55
                else
116
55
                {
117
55
                    stops[count].color[0] = sample[0];
118
55
                    stops[count].color[1] = sample[1];
119
55
                    stops[count].color[2] = sample[2];
120
55
                    stops[count].color[3] = sample[3];
121
55
                }
122
55
                rc_decrement(colorspace, "xps_parse_gradient_stops");
123
124
55
                count ++;
125
55
            }
126
55
        }
127
128
55
        if (icclink != NULL)
129
55
            gsicc_release_link(icclink);
130
55
        icclink = NULL;
131
55
        node = xps_next(node);
132
133
55
    }
134
135
22
    if (count == 0)
136
0
    {
137
0
        gs_warn("gradient brush has no gradient stops");
138
0
        stops[0].offset = 0;
139
0
        stops[0].color[0] = 1;
140
0
        stops[0].color[1] = 0;
141
0
        stops[0].color[2] = 0;
142
0
        stops[0].color[3] = 0;
143
0
        stops[1].offset = 1;
144
0
        stops[1].color[0] = 1;
145
0
        stops[1].color[1] = 1;
146
0
        stops[1].color[2] = 1;
147
0
        stops[1].color[3] = 1;
148
0
        return 2;
149
0
    }
150
151
22
    if (count == maxcount)
152
0
        gs_warn("gradient brush exceeded maximum number of gradient stops");
153
154
    /* Postprocess to make sure the range of offsets is 0.0 to 1.0 */
155
156
22
    qsort(stops, count, sizeof(struct stop), cmp_stop);
157
158
22
    before = -1;
159
22
    after = -1;
160
161
77
    for (i = 0; i < count; i++)
162
55
    {
163
55
        if (stops[i].offset < 0)
164
0
            before = i;
165
55
        if (stops[i].offset > 1)
166
0
        {
167
0
            after = i;
168
0
            break;
169
0
        }
170
55
    }
171
172
    /* Remove all stops < 0 except the largest one */
173
22
    if (before > 0)
174
0
    {
175
0
        memmove(stops, stops + before, (count - before) * sizeof(struct stop));
176
0
        count -= before;
177
0
    }
178
179
    /* Remove all stops > 1 except the smallest one */
180
22
    if (after >= 0)
181
0
        count = after + 1;
182
183
    /* Expand single stop to 0 .. 1 */
184
22
    if (count == 1)
185
0
    {
186
0
        stops[1] = stops[0];
187
0
        stops[0].offset = 0;
188
0
        stops[1].offset = 1;
189
0
        return 2;
190
0
    }
191
192
    /* First stop < 0 -- interpolate value to 0 */
193
22
    if (stops[0].offset < 0)
194
0
    {
195
0
        float d = -stops[0].offset / (stops[1].offset - stops[0].offset);
196
0
        stops[0].offset = 0;
197
0
        for (k = 0; k < 4; k++)
198
0
            stops[0].color[k] = lerp(stops[0].color[k], stops[1].color[k], d);
199
0
    }
200
201
    /* Last stop > 1 -- interpolate value to 1 */
202
22
    if (stops[count-1].offset > 1)
203
0
    {
204
0
        float d = (1 - stops[count-2].offset) / (stops[count-1].offset - stops[count-2].offset);
205
0
        stops[count-1].offset = 1;
206
0
        for (k = 0; k < 4; k++)
207
0
            stops[count-1].color[k] = lerp(stops[count-2].color[k], stops[count-1].color[k], d);
208
0
    }
209
210
    /* First stop > 0 -- insert a duplicate at 0 */
211
22
    if (stops[0].offset > 0)
212
0
    {
213
0
        memmove(stops + 1, stops, count * sizeof(struct stop));
214
0
        stops[0] = stops[1];
215
0
        stops[0].offset = 0;
216
0
        count++;
217
0
    }
218
219
    /* Last stop < 1 -- insert a duplicate at 1 */
220
22
    if (stops[count-1].offset < 1)
221
0
    {
222
0
        stops[count] = stops[count-1];
223
0
        stops[count].offset = 1;
224
0
        count++;
225
0
    }
226
227
22
    return count;
228
22
}
229
230
static int
231
xps_gradient_has_transparent_colors(struct stop *stops, int count)
232
22
{
233
22
    int i;
234
77
    for (i = 0; i < count; i++)
235
55
        if (stops[i].color[0] < 1)
236
0
            return 1;
237
22
    return 0;
238
22
}
239
240
/*
241
 * Create a Function object to map [0..1] to RGB colors
242
 * based on the gradient stop arrays.
243
 *
244
 * We do this by creating a stitching function that joins
245
 * a series of linear functions (one linear function
246
 * for each gradient stop-pair).
247
 */
248
249
static gs_function_t *
250
xps_create_gradient_stop_function(xps_context_t *ctx, struct stop *stops, int count, int opacity_only)
251
44
{
252
44
    gs_function_1ItSg_params_t sparams;
253
44
    gs_function_ElIn_params_t lparams;
254
44
    gs_function_t *sfunc;
255
44
    gs_function_t *lfunc;
256
257
44
    float *domain, *range, *c0, *c1, *bounds, *encode;
258
44
    const gs_function_t **functions;
259
260
44
    int code;
261
44
    int k;
262
44
    int i;
263
264
44
    k = count - 1; /* number of intervals / functions */
265
44
    if (k > INT_MAX / sizeof(float)) {
266
0
        gs_throw(gs_error_limitcheck, "out of memory: range\n");
267
0
        return NULL;
268
0
    }
269
270
44
    domain = xps_alloc(ctx, 2 * sizeof(float));
271
44
    if (!domain) {
272
0
        gs_throw(gs_error_VMerror, "out of memory: domain\n");
273
0
        return NULL;
274
0
    }
275
44
    domain[0] = 0.0;
276
44
    domain[1] = 1.0;
277
44
    sparams.m = 1;
278
44
    sparams.Domain = domain;
279
280
44
    range = xps_alloc(ctx, 6 * sizeof(float));
281
44
    if (!range) {
282
0
        gs_throw(gs_error_VMerror, "out of memory: range\n");
283
0
        return NULL;
284
0
    }
285
44
    range[0] = 0.0;
286
44
    range[1] = 1.0;
287
44
    range[2] = 0.0;
288
44
    range[3] = 1.0;
289
44
    range[4] = 0.0;
290
44
    range[5] = 1.0;
291
44
    sparams.Range = range;
292
293
44
    functions = xps_alloc(ctx, (size_t)k * sizeof(void*));
294
44
    if (!functions) {
295
0
        gs_throw(gs_error_VMerror, "out of memory: functions.\n");
296
0
        return NULL;
297
0
    }
298
44
    bounds = xps_alloc(ctx, ((size_t)k - 1) * sizeof(float));
299
44
    if (!bounds) {
300
0
        gs_throw(gs_error_VMerror, "out of memory: bounds.\n");
301
0
        return NULL;
302
0
    }
303
44
    encode = xps_alloc(ctx, ((size_t)k * 2) * sizeof(float));
304
44
    if (!encode) {
305
0
        gs_throw(gs_error_VMerror, "out of memory: encode.\n");
306
0
        return NULL;
307
0
    }
308
309
44
    sparams.k = k;
310
44
    sparams.Functions = functions;
311
44
    sparams.Bounds = bounds;
312
44
    sparams.Encode = encode;
313
314
44
    if (opacity_only)
315
22
    {
316
22
        sparams.n = 1;
317
22
        lparams.n = 1;
318
22
    }
319
22
    else
320
22
    {
321
22
        sparams.n = 3;
322
22
        lparams.n = 3;
323
22
    }
324
325
110
    for (i = 0; i < k; i++)
326
66
    {
327
66
        domain = xps_alloc(ctx, 2 * sizeof(float));
328
66
        if (!domain) {
329
0
            gs_throw(gs_error_VMerror, "out of memory: domain.\n");
330
0
            return NULL;
331
0
    }
332
66
        domain[0] = 0.0;
333
66
        domain[1] = 1.0;
334
66
        lparams.m = 1;
335
66
        lparams.Domain = domain;
336
337
66
        range = xps_alloc(ctx, 6 * sizeof(float));
338
66
        if (!range) {
339
0
            gs_throw(gs_error_VMerror, "out of memory: range.\n");
340
0
            return NULL;
341
0
        }
342
66
        range[0] = 0.0;
343
66
        range[1] = 1.0;
344
66
        range[2] = 0.0;
345
66
        range[3] = 1.0;
346
66
        range[4] = 0.0;
347
66
        range[5] = 1.0;
348
66
        lparams.Range = range;
349
350
66
        c0 = xps_alloc(ctx, 3 * sizeof(float));
351
66
        if (!c0) {
352
0
            gs_throw(gs_error_VMerror, "out of memory: c0.\n");
353
0
            return NULL;
354
0
        }
355
66
        lparams.C0 = c0;
356
357
66
        c1 = xps_alloc(ctx, 3 * sizeof(float));
358
66
        if (!c1) {
359
0
            gs_throw(gs_error_VMerror, "out of memory: c1.\n");
360
0
            return NULL;
361
0
        }
362
66
        lparams.C1 = c1;
363
364
66
        if (opacity_only)
365
33
        {
366
33
            c0[0] = stops[i].color[0];
367
33
            c1[0] = stops[i+1].color[0];
368
33
        }
369
33
        else
370
33
        {
371
33
            c0[0] = stops[i].color[1];
372
33
            c0[1] = stops[i].color[2];
373
33
            c0[2] = stops[i].color[3];
374
375
33
            c1[0] = stops[i+1].color[1];
376
33
            c1[1] = stops[i+1].color[2];
377
33
            c1[2] = stops[i+1].color[3];
378
33
        }
379
380
66
        lparams.N = 1;
381
382
66
        code = gs_function_ElIn_init(&lfunc, &lparams, ctx->memory);
383
66
        if (code < 0)
384
0
        {
385
0
            gs_rethrow(code, "gs_function_ElIn_init failed");
386
0
            return NULL;
387
0
        }
388
389
66
        functions[i] = lfunc;
390
391
66
        if (i > 0)
392
22
            bounds[i - 1] = stops[i].offset;
393
394
66
        encode[i * 2 + 0] = 0.0;
395
66
        encode[i * 2 + 1] = 1.0;
396
66
    }
397
398
44
    code = gs_function_1ItSg_init(&sfunc, &sparams, ctx->memory);
399
44
    if (code < 0)
400
0
    {
401
0
        gs_rethrow(code, "gs_function_1ItSg_init failed");
402
0
        return NULL;
403
0
    }
404
405
44
    return sfunc;
406
44
}
407
408
/*
409
 * Shadings and functions are ghostscript type objects,
410
 * and as such rely on the garbage collector for cleanup.
411
 * We can't have none of that here, so we have to
412
 * write our own destructors.
413
 */
414
415
static void
416
xps_free_gradient_stop_function(xps_context_t *ctx, gs_function_t *func)
417
44
{
418
44
    gs_function_t *lfunc;
419
44
    gs_function_1ItSg_params_t *sparams;
420
44
    gs_function_ElIn_params_t *lparams;
421
44
    int i;
422
423
44
    sparams = (gs_function_1ItSg_params_t*) &func->params;
424
44
    xps_free(ctx, (void*)sparams->Domain);
425
44
    xps_free(ctx, (void*)sparams->Range);
426
427
110
    for (i = 0; i < sparams->k; i++)
428
66
    {
429
66
        lfunc = (gs_function_t*) sparams->Functions[i]; /* discard const */
430
66
        lparams = (gs_function_ElIn_params_t*) &lfunc->params;
431
66
        xps_free(ctx, (void*)lparams->Domain);
432
66
        xps_free(ctx, (void*)lparams->Range);
433
66
        xps_free(ctx, (void*)lparams->C0);
434
66
        xps_free(ctx, (void*)lparams->C1);
435
66
        xps_free(ctx, lfunc);
436
66
    }
437
438
44
    xps_free(ctx, (void*)sparams->Bounds);
439
44
    xps_free(ctx, (void*)sparams->Encode);
440
44
    xps_free(ctx, (void*)sparams->Functions);
441
44
    xps_free(ctx, func);
442
44
}
443
444
/*
445
 * For radial gradients that have a cone drawing we have to
446
 * reverse the direction of the gradient because we draw
447
 * the shading in the opposite direction with the
448
 * big circle first.
449
 */
450
static gs_function_t *
451
xps_reverse_function(xps_context_t *ctx, gs_function_t *func, float *fary, void *vary)
452
0
{
453
0
    gs_function_1ItSg_params_t sparams;
454
0
    gs_function_t *sfunc;
455
0
    int code;
456
457
    /* take from stack allocated arrays that the caller provides */
458
0
    float *domain = fary + 0;
459
0
    float *range = fary + 2;
460
0
    float *encode = fary + 2 + 6;
461
0
    const gs_function_t **functions = vary;
462
463
0
    domain[0] = 0.0;
464
0
    domain[1] = 1.0;
465
466
0
    range[0] = 0.0;
467
0
    range[1] = 1.0;
468
0
    range[2] = 0.0;
469
0
    range[3] = 1.0;
470
0
    range[4] = 0.0;
471
0
    range[5] = 1.0;
472
473
0
    functions[0] = func;
474
475
0
    encode[0] = 1.0;
476
0
    encode[1] = 0.0;
477
478
0
    sparams.m = 1;
479
0
    sparams.Domain = domain;
480
0
    sparams.Range = range;
481
0
    sparams.k = 1;
482
0
    sparams.Functions = functions;
483
0
    sparams.Bounds = NULL;
484
0
    sparams.Encode = encode;
485
486
0
    if (ctx->opacity_only)
487
0
        sparams.n = 1;
488
0
    else
489
0
        sparams.n = 3;
490
491
0
    code = gs_function_1ItSg_init(&sfunc, &sparams, ctx->memory);
492
0
    if (code < 0)
493
0
    {
494
0
        gs_rethrow(code, "gs_function_1ItSg_init failed");
495
0
        return NULL;
496
0
    }
497
498
0
    return sfunc;
499
0
}
500
501
/*
502
 * Radial gradients map more or less to Radial shadings.
503
 * The inner circle is always a point.
504
 * The outer circle is actually an ellipse,
505
 * mess with the transform to squash the circle into the right aspect.
506
 */
507
508
static int
509
xps_draw_one_radial_gradient(xps_context_t *ctx,
510
        gs_function_t *func, int extend,
511
        float x0, float y0, float r0,
512
        float x1, float y1, float r1)
513
11
{
514
11
    gs_memory_t *mem = ctx->memory;
515
11
    gs_shading_t *shading;
516
11
    gs_shading_R_params_t params;
517
11
    int code;
518
519
11
    gs_shading_R_params_init(&params);
520
11
    {
521
11
        if (ctx->opacity_only)
522
0
            params.ColorSpace = ctx->gray_lin;
523
11
        else
524
11
            params.ColorSpace = ctx->srgb;
525
526
11
        params.Coords[0] = x0;
527
11
        params.Coords[1] = y0;
528
11
        params.Coords[2] = r0;
529
11
        params.Coords[3] = x1;
530
11
        params.Coords[4] = y1;
531
11
        params.Coords[5] = r1;
532
533
11
        params.Extend[0] = extend;
534
11
        params.Extend[1] = extend;
535
536
11
        params.Function = func;
537
11
    }
538
539
11
    code = gs_shading_R_init(&shading, &params, mem);
540
11
    if (code < 0)
541
0
        return gs_rethrow(code, "gs_shading_R_init failed");
542
543
11
    gs_setsmoothness(ctx->pgs, 0.02);
544
545
11
    code = gs_shfill(ctx->pgs, shading);
546
11
    if (code < 0)
547
0
    {
548
0
        gs_free_object(mem, shading, "gs_shading_R");
549
0
        return gs_rethrow(code, "gs_shfill failed");
550
0
    }
551
552
11
    gs_free_object(mem, shading, "gs_shading_R");
553
554
11
    return 0;
555
11
}
556
557
/*
558
 * Linear gradients map to Axial shadings.
559
 */
560
561
static int
562
xps_draw_one_linear_gradient(xps_context_t *ctx,
563
        gs_function_t *func, int extend,
564
        float x0, float y0, float x1, float y1)
565
11
{
566
11
    gs_memory_t *mem = ctx->memory;
567
11
    gs_shading_t *shading;
568
11
    gs_shading_A_params_t params;
569
11
    int code;
570
571
11
    gs_shading_A_params_init(&params);
572
11
    {
573
11
        if (ctx->opacity_only)
574
0
            params.ColorSpace = ctx->gray_lin;
575
11
        else
576
11
            params.ColorSpace = ctx->srgb;
577
578
11
        params.Coords[0] = x0;
579
11
        params.Coords[1] = y0;
580
11
        params.Coords[2] = x1;
581
11
        params.Coords[3] = y1;
582
583
11
        params.Extend[0] = extend;
584
11
        params.Extend[1] = extend;
585
586
11
        params.Function = func;
587
11
    }
588
589
11
    code = gs_shading_A_init(&shading, &params, mem);
590
11
    if (code < 0)
591
0
        return gs_rethrow(code, "gs_shading_A_init failed");
592
593
11
    gs_setsmoothness(ctx->pgs, 0.02);
594
595
11
    code = gs_shfill(ctx->pgs, shading);
596
11
    if (code < 0)
597
0
    {
598
0
        gs_free_object(mem, shading, "gs_shading_A");
599
0
        return gs_rethrow(code, "gs_shfill failed");
600
0
    }
601
602
11
    gs_free_object(mem, shading, "gs_shading_A");
603
604
11
    return 0;
605
11
}
606
607
/*
608
 * We need to loop and create many shading objects to account
609
 * for the Repeat and Reflect SpreadMethods.
610
 * I'm not smart enough to calculate this analytically
611
 * so we iterate and check each object until we
612
 * reach a reasonable limit for infinite cases.
613
 */
614
615
static inline int point_inside_circle(float px, float py, float x, float y, float r)
616
11
{
617
11
    float dx = px - x;
618
11
    float dy = py - y;
619
11
    return (dx * dx + dy * dy) <= (r * r);
620
11
}
621
622
static int
623
xps_draw_radial_gradient(xps_context_t *ctx, xps_item_t *root, int spread, gs_function_t *func)
624
11
{
625
11
    gs_rect bbox;
626
11
    float x0 = 0, y0 = 0, r0;
627
11
    float x1 = 0, y1 = 0, r1;
628
11
    float xrad = 1;
629
11
    float yrad = 1;
630
11
    float invscale;
631
11
    float dx, dy;
632
11
    int code;
633
11
    int i;
634
11
    int done;
635
636
11
    char *center_att = xps_att(root, "Center");
637
11
    char *origin_att = xps_att(root, "GradientOrigin");
638
11
    char *radius_x_att = xps_att(root, "RadiusX");
639
11
    char *radius_y_att = xps_att(root, "RadiusY");
640
641
11
    if (origin_att)
642
11
        xps_get_point(origin_att, &x0, &y0);
643
11
    if (center_att)
644
11
        xps_get_point(center_att, &x1, &y1);
645
11
    if (radius_x_att)
646
11
        xrad = atof(radius_x_att);
647
11
    if (radius_y_att)
648
11
        yrad = atof(radius_y_att);
649
650
11
    gs_gsave(ctx->pgs);
651
652
    /* scale the ctm to make ellipses */
653
11
    if (xrad != 0)
654
11
        gs_scale(ctx->pgs, 1.0, yrad / xrad);
655
656
11
    if (yrad != 0)
657
11
    {
658
11
        invscale = xrad / yrad;
659
11
        y0 = y0 * invscale;
660
11
        y1 = y1 * invscale;
661
11
    }
662
663
11
    r0 = 0.0;
664
11
    r1 = xrad;
665
666
11
    dx = x1 - x0;
667
11
    dy = y1 - y0;
668
669
11
    xps_bounds_in_user_space(ctx, &bbox);
670
671
11
    if (spread == SPREAD_PAD)
672
11
    {
673
11
        if (!point_inside_circle(x0, y0, x1, y1, r1))
674
0
        {
675
0
            gs_function_t *reverse;
676
0
            float in[1];
677
0
            float out[4];
678
0
            float fary[10];
679
0
            void *vary[1];
680
681
            /* PDF shadings with extend doesn't work the same way as XPS
682
             * gradients when the radial shading is a cone. In this case
683
             * we fill the background ourselves.
684
             */
685
686
0
            in[0] = 1.0;
687
0
            out[0] = 1.0;
688
0
            out[1] = 0.0;
689
0
            out[2] = 0.0;
690
0
            out[3] = 0.0;
691
0
            if (ctx->opacity_only)
692
0
            {
693
0
                gs_function_evaluate(func, in, out);
694
0
                xps_set_color(ctx, ctx->gray_lin, out);
695
0
            }
696
0
            else
697
0
            {
698
0
                gs_function_evaluate(func, in, out + 1);
699
0
                xps_set_color(ctx, ctx->srgb, out);
700
0
            }
701
702
0
            gs_moveto(ctx->pgs, bbox.p.x, bbox.p.y);
703
0
            gs_lineto(ctx->pgs, bbox.q.x, bbox.p.y);
704
0
            gs_lineto(ctx->pgs, bbox.q.x, bbox.q.y);
705
0
            gs_lineto(ctx->pgs, bbox.p.x, bbox.q.y);
706
0
            gs_closepath(ctx->pgs);
707
0
            gs_fill(ctx->pgs);
708
709
            /* We also have to reverse the direction so the bigger circle
710
             * comes first or the graphical results do not match. We also
711
             * have to reverse the direction of the function to compensate.
712
             */
713
714
0
            reverse = xps_reverse_function(ctx, func, fary, vary);
715
0
            if (!reverse)
716
0
            {
717
0
                gs_grestore(ctx->pgs);
718
0
                return gs_rethrow(-1, "could not create the reversed function");
719
0
            }
720
721
0
            code = xps_draw_one_radial_gradient(ctx, reverse, 1, x1, y1, r1, x0, y0, r0);
722
0
            if (code < 0)
723
0
            {
724
0
                xps_free(ctx, reverse);
725
0
                gs_grestore(ctx->pgs);
726
0
                return gs_rethrow(code, "could not draw radial gradient");
727
0
            }
728
729
0
            xps_free(ctx, reverse);
730
0
        }
731
11
        else
732
11
        {
733
11
            code = xps_draw_one_radial_gradient(ctx, func, 1, x0, y0, r0, x1, y1, r1);
734
11
            if (code < 0)
735
0
            {
736
0
                gs_grestore(ctx->pgs);
737
0
                return gs_rethrow(code, "could not draw radial gradient");
738
0
            }
739
11
        }
740
11
    }
741
0
    else
742
0
    {
743
0
        for (i = 0; i < 100; i++)
744
0
        {
745
            /* Draw current circle */
746
747
0
            if (!point_inside_circle(x0, y0, x1, y1, r1))
748
0
                dmputs(ctx->memory, "xps: we should reverse gradient here too\n");
749
750
0
            if (spread == SPREAD_REFLECT && (i & 1))
751
0
                code = xps_draw_one_radial_gradient(ctx, func, 0, x1, y1, r1, x0, y0, r0);
752
0
            else
753
0
                code = xps_draw_one_radial_gradient(ctx, func, 0, x0, y0, r0, x1, y1, r1);
754
0
            if (code < 0)
755
0
            {
756
0
                gs_grestore(ctx->pgs);
757
0
                return gs_rethrow(code, "could not draw axial gradient");
758
0
            }
759
760
            /* Check if circle encompassed the entire bounding box (break loop if we do) */
761
762
0
            done = 1;
763
0
            if (!point_inside_circle(bbox.p.x, bbox.p.y, x1, y1, r1)) done = 0;
764
0
            if (!point_inside_circle(bbox.p.x, bbox.q.y, x1, y1, r1)) done = 0;
765
0
            if (!point_inside_circle(bbox.q.x, bbox.q.y, x1, y1, r1)) done = 0;
766
0
            if (!point_inside_circle(bbox.q.x, bbox.p.y, x1, y1, r1)) done = 0;
767
0
            if (done)
768
0
                break;
769
770
            /* Prepare next circle */
771
772
0
            r0 = r1;
773
0
            r1 += xrad;
774
775
0
            x0 += dx;
776
0
            y0 += dy;
777
0
            x1 += dx;
778
0
            y1 += dy;
779
0
        }
780
0
    }
781
782
11
    gs_grestore(ctx->pgs);
783
784
11
    return 0;
785
11
}
786
787
/*
788
 * Calculate how many iterations are needed to cover
789
 * the bounding box.
790
 */
791
792
static int
793
xps_draw_linear_gradient(xps_context_t *ctx, xps_item_t *root, int spread, gs_function_t *func)
794
11
{
795
11
    gs_rect bbox;
796
11
    float x0, y0, x1, y1;
797
11
    float dx, dy;
798
11
    int code;
799
11
    int i;
800
11
    float len;
801
11
    gs_point pt;
802
803
11
    char *start_point_att = xps_att(root, "StartPoint");
804
11
    char *end_point_att = xps_att(root, "EndPoint");
805
806
11
    x0 = 0;
807
11
    y0 = 0;
808
11
    x1 = 0;
809
11
    y1 = 1;
810
811
11
    if (start_point_att)
812
11
        xps_get_point(start_point_att, &x0, &y0);
813
11
    if (end_point_att)
814
11
        xps_get_point(end_point_att, &x1, &y1);
815
816
11
    dx = x1 - x0;
817
11
    dy = y1 - y0;
818
819
11
    xps_bounds_in_user_space(ctx, &bbox);
820
821
11
    len = sqrt(dx * dx + dy * dy);
822
    /* transfrom the 'len' into device spaces */
823
11
    gs_distance_transform(0, len, &ctm_only(ctx->pgs), &pt);
824
    /* If *both* the x and y distances are under half a pixel, ignore the gradient
825
     * (it is apparently possible for either to be zero...)
826
     */
827
11
    if (fabs(pt.x) < 0.5 && fabs(pt.y) < 0.5)
828
0
        spread = SPREAD_PAD;
829
830
11
    if (spread == SPREAD_PAD)
831
11
    {
832
11
        code = xps_draw_one_linear_gradient(ctx, func, 1, x0, y0, x1, y1);
833
11
        if (code < 0)
834
0
            return gs_rethrow(code, "could not draw axial gradient");
835
11
    }
836
0
    else
837
0
    {
838
0
        float a, b;
839
0
        float dist[4];
840
0
        float d0, d1;
841
0
        int i0, i1;
842
843
0
        a = dx / len;
844
0
        b = dy / len;
845
846
0
        dist[0] = a * (bbox.p.x - x0) + b * (bbox.p.y - y0);
847
0
        dist[1] = a * (bbox.p.x - x0) + b * (bbox.q.y - y0);
848
0
        dist[2] = a * (bbox.q.x - x0) + b * (bbox.q.y - y0);
849
0
        dist[3] = a * (bbox.q.x - x0) + b * (bbox.p.y - y0);
850
851
0
        d0 = dist[0];
852
0
        d1 = dist[0];
853
0
        for (i = 1; i < 4; i++)
854
0
        {
855
0
            if (dist[i] < d0) d0 = dist[i];
856
0
            if (dist[i] > d1) d1 = dist[i];
857
0
        }
858
859
0
        i0 = (int)floor(d0 / len);
860
0
        i1 = (int)ceil(d1 / len);
861
862
0
        for (i = i0; i < i1; i++)
863
0
        {
864
0
            if (spread == SPREAD_REFLECT && (i & 1))
865
0
            {
866
0
                code = xps_draw_one_linear_gradient(ctx, func, 0,
867
0
                        x1 + dx * i, y1 + dy * i,
868
0
                        x0 + dx * i, y0 + dy * i);
869
0
            }
870
0
            else
871
0
            {
872
0
                code = xps_draw_one_linear_gradient(ctx, func, 0,
873
0
                        x0 + dx * i, y0 + dy * i,
874
0
                        x1 + dx * i, y1 + dy * i);
875
0
            }
876
0
            if (code < 0)
877
0
                return gs_rethrow(code, "could not draw axial gradient");
878
0
        }
879
0
    }
880
881
11
    return 0;
882
11
}
883
884
/*
885
 * Parse XML tag and attributes for a gradient brush, create color/opacity
886
 * function objects and call gradient drawing primitives.
887
 */
888
889
static int
890
xps_parse_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root,
891
        int (*draw)(xps_context_t *, xps_item_t *, int, gs_function_t *))
892
22
{
893
22
    xps_item_t *node;
894
895
22
    char *opacity_att;
896
    /*char *interpolation_att;*/
897
22
    char *spread_att;
898
    /*char *mapping_att;*/
899
22
    char *transform_att;
900
901
22
    xps_item_t *transform_tag = NULL;
902
22
    xps_item_t *stop_tag = NULL;
903
904
22
    struct stop stop_list[MAX_STOPS];
905
22
    int stop_count;
906
22
    gs_matrix transform;
907
22
    int spread_method;
908
22
    int code;
909
910
22
    gs_rect bbox;
911
912
22
    gs_function_t *color_func;
913
22
    gs_function_t *opacity_func;
914
22
    int has_opacity = 0;
915
916
22
    opacity_att = xps_att(root, "Opacity");
917
    /*interpolation_att = xps_att(root, "ColorInterpolationMode");*/
918
22
    spread_att = xps_att(root, "SpreadMethod");
919
    /*mapping_att = xps_att(root, "MappingMode");*/
920
22
    transform_att = xps_att(root, "Transform");
921
922
44
    for (node = xps_down(root); node; node = xps_next(node))
923
22
    {
924
22
        if (!strcmp(xps_tag(node), "LinearGradientBrush.Transform"))
925
0
            transform_tag = xps_down(node);
926
22
        if (!strcmp(xps_tag(node), "RadialGradientBrush.Transform"))
927
0
            transform_tag = xps_down(node);
928
22
        if (!strcmp(xps_tag(node), "LinearGradientBrush.GradientStops"))
929
11
            stop_tag = xps_down(node);
930
22
        if (!strcmp(xps_tag(node), "RadialGradientBrush.GradientStops"))
931
11
            stop_tag = xps_down(node);
932
22
    }
933
934
22
    xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
935
936
22
    spread_method = SPREAD_PAD;
937
22
    if (spread_att)
938
0
    {
939
0
        if (!strcmp(spread_att, "Pad"))
940
0
            spread_method = SPREAD_PAD;
941
0
        if (!strcmp(spread_att, "Reflect"))
942
0
            spread_method = SPREAD_REFLECT;
943
0
        if (!strcmp(spread_att, "Repeat"))
944
0
            spread_method = SPREAD_REPEAT;
945
0
    }
946
947
22
    gs_make_identity(&transform);
948
22
    if (transform_att)
949
0
        xps_parse_render_transform(ctx, transform_att, &transform);
950
22
    if (transform_tag)
951
0
        xps_parse_matrix_transform(ctx, transform_tag, &transform);
952
953
22
    if (!stop_tag)
954
0
        return gs_throw(-1, "missing gradient stops tag");
955
956
22
    stop_count = xps_parse_gradient_stops(ctx, base_uri, stop_tag, stop_list, MAX_STOPS);
957
22
    if (stop_count == 0)
958
0
        return gs_throw(-1, "no gradient stops found");
959
960
22
    color_func = xps_create_gradient_stop_function(ctx, stop_list, stop_count, 0);
961
22
    if (!color_func)
962
0
        return gs_rethrow(-1, "could not create color gradient function");
963
964
22
    opacity_func = xps_create_gradient_stop_function(ctx, stop_list, stop_count, 1);
965
22
    if (!opacity_func)
966
0
        return gs_rethrow(-1, "could not create opacity gradient function");
967
968
22
    has_opacity = xps_gradient_has_transparent_colors(stop_list, stop_count);
969
970
22
    xps_clip(ctx);
971
972
22
    gs_gsave(ctx->pgs);
973
22
    gs_concat(ctx->pgs, &transform);
974
975
22
    xps_bounds_in_user_space(ctx, &bbox);
976
977
22
    code = xps_begin_opacity(ctx, base_uri, dict, opacity_att, NULL, false, false);
978
22
    if (code)
979
0
    {
980
0
        gs_grestore(ctx->pgs);
981
0
        return gs_rethrow(code, "cannot create transparency group");
982
0
    }
983
984
22
    if (ctx->opacity_only)
985
0
    {
986
0
        code = draw(ctx, root, spread_method, opacity_func);
987
0
        if (code)
988
0
        {
989
0
            gs_grestore(ctx->pgs);
990
0
            return gs_rethrow(code, "cannot draw gradient opacity");
991
0
        }
992
0
    }
993
22
    else
994
22
    {
995
22
        if (has_opacity)
996
0
        {
997
0
            gs_transparency_mask_params_t params;
998
0
            gs_transparency_group_params_t tgp;
999
1000
0
            gs_setblendmode(ctx->pgs, BLEND_MODE_Normal);
1001
0
            gs_trans_mask_params_init(&params, TRANSPARENCY_MASK_Luminosity);
1002
0
            params.ColorSpace = gs_currentcolorspace_inline(ctx->pgs);
1003
0
            gs_begin_transparency_mask(ctx->pgs, &params, &bbox, 0);
1004
            /* I dont like this, but dont want to change interface of draw */
1005
            /* For the opacity case, we want to make sure the functions
1006
               are set up for gray only */
1007
0
            ctx->opacity_only = true;
1008
0
            code = draw(ctx, root, spread_method, opacity_func);
1009
0
            ctx->opacity_only = false;
1010
0
            if (code)
1011
0
            {
1012
0
                gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
1013
0
                gs_grestore(ctx->pgs);
1014
0
                return gs_rethrow(code, "cannot draw gradient opacity");
1015
0
            }
1016
0
            gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
1017
1018
0
            gs_trans_group_params_init(&tgp, 1.0);
1019
0
            gs_begin_transparency_group(ctx->pgs, &tgp, &bbox, PDF14_BEGIN_TRANS_GROUP);
1020
0
            code = draw(ctx, root, spread_method, color_func);
1021
0
            if (code)
1022
0
            {
1023
0
                gs_end_transparency_group(ctx->pgs);
1024
0
                gs_grestore(ctx->pgs);
1025
0
                return gs_rethrow(code, "cannot draw gradient color");
1026
0
            }
1027
0
            gs_end_transparency_group(ctx->pgs);
1028
            /* Need to remove the soft mask from the graphic state.  Otherwise
1029
               we may end up using it in subsequent drawings.  Note that there
1030
               is not a push of the state made since there is already a soft
1031
               mask present from gs_end_transparency_mask.  In this case,
1032
               we are removing the mask with this forced pop. */
1033
0
            gs_pop_transparency_state(ctx->pgs, true);
1034
0
        }
1035
22
        else
1036
22
        {
1037
22
            code = draw(ctx, root, spread_method, color_func);
1038
22
            if (code)
1039
0
            {
1040
0
                gs_grestore(ctx->pgs);
1041
0
                return gs_rethrow(code, "cannot draw gradient color");
1042
0
            }
1043
22
        }
1044
22
    }
1045
1046
22
    xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
1047
1048
22
    gs_grestore(ctx->pgs);
1049
1050
22
    xps_free_gradient_stop_function(ctx, opacity_func);
1051
22
    xps_free_gradient_stop_function(ctx, color_func);
1052
1053
22
    return 0;
1054
22
}
1055
1056
int
1057
xps_parse_linear_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
1058
11
{
1059
11
    int code;
1060
11
    code = xps_parse_gradient_brush(ctx, base_uri, dict, root, xps_draw_linear_gradient);
1061
11
    if (code < 0)
1062
0
        return gs_rethrow(code, "cannot parse linear gradient brush");
1063
11
    return gs_okay;
1064
11
}
1065
1066
int
1067
xps_parse_radial_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
1068
11
{
1069
11
    int code;
1070
11
    code = xps_parse_gradient_brush(ctx, base_uri, dict, root, xps_draw_radial_gradient);
1071
11
    if (code < 0)
1072
0
        return gs_rethrow(code, "cannot parse radial gradient brush");
1073
11
    return gs_okay;
1074
11
}