Coverage Report

Created: 2026-08-08 08:00

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