summaryrefslogtreecommitdiff
path: root/xps/xpsgradient.c
diff options
context:
space:
mode:
Diffstat (limited to 'xps/xpsgradient.c')
-rw-r--r--xps/xpsgradient.c778
1 files changed, 778 insertions, 0 deletions
diff --git a/xps/xpsgradient.c b/xps/xpsgradient.c
new file mode 100644
index 000000000..9bcc9cdc3
--- /dev/null
+++ b/xps/xpsgradient.c
@@ -0,0 +1,778 @@
+#include "ghostxps.h"
+
+#include "../icclib/icc.h"
+
+#define MAX_STOPS 100
+
+enum { SPREAD_PAD, SPREAD_REPEAT, SPREAD_REFLECT };
+
+/*
+ * Parse a list of GradientStop elements.
+ * Fill the offset and color arrays, and
+ * return the number of stops parsed.
+ */
+
+static int
+xps_parse_gradient_stops(xps_context_t *ctx, xps_item_t *node,
+ float *offsets, float *colors, int maxcount)
+{
+ int count = 0;
+ gs_color_space *colorspace;
+ float sample[32];
+
+ while (node && count < maxcount)
+ {
+ if (!strcmp(xps_tag(node), "GradientStop"))
+ {
+ char *offset = xps_att(node, "Offset");
+ char *color = xps_att(node, "Color");
+ if (offset && color)
+ {
+ offsets[count] = atof(offset);
+
+ xps_parse_color(ctx, color, &colorspace, sample);
+
+ /* Convert color to sRGB by calling ICClib directly */
+
+ if (colorspace->type->index == gs_color_space_index_CIEICC)
+ {
+ double inv[32];
+ double xyz[3];
+ int i;
+
+ struct _icc *icc = colorspace->params.icc.picc_info->picc;
+ struct _icmLuBase *lu = colorspace->params.icc.picc_info->plu;
+
+ for (i = 0; i < cs_num_components(colorspace); i++)
+ inv[i] = sample[i + 1];
+
+ lu->lookup(lu, inv + 1, xyz);
+
+ dprintf3("gradient convert to xyz: %g %g %g\n", xyz[0], xyz[1], xyz[2]);
+ }
+
+ colors[count * 4 + 0] = sample[0];
+ colors[count * 4 + 1] = sample[1];
+ colors[count * 4 + 2] = sample[2];
+ colors[count * 4 + 3] = sample[3];
+ count ++;
+ }
+ }
+
+ node = xps_next(node);
+ }
+
+ return count;
+}
+
+static int
+xps_gradient_has_transparent_colors(float *offsets, float *colors, int count)
+{
+ int i;
+ for (i = 0; i < count; i++)
+ if (colors[i * 4 + 0] < 0.999)
+ return 1;
+ return 0;
+}
+
+/*
+ * Create a Function object to map [0..1] to RGB colors
+ * based on the gradient stop arrays.
+ *
+ * We do this by creating a stitching function that joins
+ * a series of linear functions (one linear function
+ * for each gradient stop-pair).
+ */
+
+static gs_function_t *
+xps_create_gradient_stop_function(xps_context_t *ctx,
+ float *offsets, float *colors, int count, int opacity_only)
+{
+ gs_function_1ItSg_params_t sparams;
+ gs_function_ElIn_params_t lparams;
+ gs_function_t *sfunc;
+ gs_function_t *lfunc;
+
+ float *domain, *range, *c0, *c1, *bounds, *encode;
+ const gs_function_t **functions;
+
+ int code;
+ int k;
+ int i;
+
+ k = count - 1; /* number of intervals / functions */
+
+ domain = xps_alloc(ctx, 2 * sizeof(float));
+ domain[0] = 0.0;
+ domain[1] = 1.0;
+ sparams.m = 1;
+ sparams.Domain = domain;
+
+ range = xps_alloc(ctx, 6 * sizeof(float));
+ range[0] = 0.0;
+ range[1] = 1.0;
+ range[2] = 0.0;
+ range[3] = 1.0;
+ range[4] = 0.0;
+ range[5] = 1.0;
+ sparams.n = 3;
+ sparams.Range = range;
+
+ functions = xps_alloc(ctx, k * sizeof(void*));
+ bounds = xps_alloc(ctx, (k - 1) * sizeof(float));
+ encode = xps_alloc(ctx, (k * 2) * sizeof(float));
+
+ sparams.k = k;
+ sparams.Functions = functions;
+ sparams.Bounds = bounds;
+ sparams.Encode = encode;
+
+ for (i = 0; i < k; i++)
+ {
+ domain = xps_alloc(ctx, 2 * sizeof(float));
+ domain[0] = 0.0;
+ domain[1] = 1.0;
+ lparams.m = 1;
+ lparams.Domain = domain;
+
+ range = xps_alloc(ctx, 6 * sizeof(float));
+ range[0] = 0.0;
+ range[1] = 1.0;
+ range[2] = 0.0;
+ range[3] = 1.0;
+ range[4] = 0.0;
+ range[5] = 1.0;
+ lparams.n = 3;
+ lparams.Range = range;
+
+ c0 = xps_alloc(ctx, 3 * sizeof(float));
+ lparams.C0 = c0;
+
+ c1 = xps_alloc(ctx, 3 * sizeof(float));
+ lparams.C1 = c1;
+
+ if (opacity_only)
+ {
+ c0[0] = colors[(i + 0) * 4 + 0];
+ c0[1] = colors[(i + 0) * 4 + 0];
+ c0[2] = colors[(i + 0) * 4 + 0];
+
+ c1[0] = colors[(i + 1) * 4 + 0];
+ c1[1] = colors[(i + 1) * 4 + 0];
+ c1[2] = colors[(i + 1) * 4 + 0];
+ }
+ else
+ {
+ c0[0] = colors[(i + 0) * 4 + 1];
+ c0[1] = colors[(i + 0) * 4 + 2];
+ c0[2] = colors[(i + 0) * 4 + 3];
+
+ c1[0] = colors[(i + 1) * 4 + 1];
+ c1[1] = colors[(i + 1) * 4 + 2];
+ c1[2] = colors[(i + 1) * 4 + 3];
+ }
+
+ lparams.N = 1;
+
+ code = gs_function_ElIn_init(&lfunc, &lparams, ctx->memory);
+ if (code < 0)
+ {
+ gs_throw(-1, "gs_function_ElIn_init failed");
+ return NULL;
+ }
+
+ functions[i] = lfunc;
+
+ if (i > 0)
+ bounds[i - 1] = offsets[(i + 0) + 0];
+
+ encode[i * 2 + 0] = 0.0;
+ encode[i * 2 + 1] = 1.0;
+ }
+
+ code = gs_function_1ItSg_init(&sfunc, &sparams, ctx->memory);
+ if (code < 0)
+ {
+ gs_throw(-1, "gs_function_1ItSg_init failed");
+ return NULL;
+ }
+
+ return sfunc;
+}
+
+/*
+ * Shadings and functions are ghostscript type objects,
+ * and as such rely on the garbage collector for cleanup.
+ * We can't have none of that here, so we have to
+ * write our own destructors.
+ */
+
+static void
+xps_free_gradient_stop_function(xps_context_t *ctx, gs_function_t *func)
+{
+ gs_function_t *lfunc;
+ gs_function_1ItSg_params_t *sparams;
+ gs_function_ElIn_params_t *lparams;
+ int i;
+
+ sparams = (gs_function_1ItSg_params_t*) &func->params;
+ xps_free(ctx, (void*)sparams->Domain);
+ xps_free(ctx, (void*)sparams->Range);
+
+ for (i = 0; i < sparams->k; i++)
+ {
+ lfunc = sparams->Functions[i];
+ lparams = (gs_function_ElIn_params_t*) &lfunc->params;
+ xps_free(ctx, (void*)lparams->Domain);
+ xps_free(ctx, (void*)lparams->Range);
+ xps_free(ctx, (void*)lparams->C0);
+ xps_free(ctx, (void*)lparams->C1);
+ xps_free(ctx, lfunc);
+ }
+
+ xps_free(ctx, (void*)sparams->Bounds);
+ xps_free(ctx, (void*)sparams->Encode);
+ xps_free(ctx, (void*)sparams->Functions);
+ xps_free(ctx, func);
+}
+
+/*
+ * For radial gradients that have a cone drawing we have to
+ * reverse the direction of the gradient because we draw
+ * the shading in the opposite direction with the
+ * big circle first.
+ */
+static gs_function_t *
+xps_reverse_function(xps_context_t *ctx, gs_function_t *func, float *fary, void *vary)
+{
+ gs_function_1ItSg_params_t sparams;
+ gs_function_t *sfunc;
+ int code;
+
+ /* take from stack allocated arrays that the caller provides */
+ float *domain = fary + 0;
+ float *range = fary + 2;
+ float *encode = fary + 2 + 6;
+ const gs_function_t **functions = vary;
+
+ domain[0] = 0.0;
+ domain[1] = 1.0;
+
+ range[0] = 0.0;
+ range[1] = 1.0;
+ range[2] = 0.0;
+ range[3] = 1.0;
+ range[4] = 0.0;
+ range[5] = 1.0;
+
+ functions[0] = func;
+
+ encode[0] = 1.0;
+ encode[1] = 0.0;
+
+ sparams.m = 1;
+ sparams.Domain = domain;
+ sparams.n = 3;
+ sparams.Range = range;
+ sparams.k = 1;
+ sparams.Functions = functions;
+ sparams.Bounds = NULL;
+ sparams.Encode = encode;
+
+ code = gs_function_1ItSg_init(&sfunc, &sparams, ctx->memory);
+ if (code < 0)
+ {
+ gs_throw(-1, "gs_function_1ItSg_init failed");
+ return NULL;
+ }
+
+ return sfunc;
+}
+
+/*
+ * Radial gradients map more or less to Radial shadings.
+ * The inner circle is always a point.
+ * The outer circle is actually an ellipse,
+ * mess with the transform to squash the circle into the right aspect.
+ */
+
+static int
+xps_draw_one_radial_gradient(xps_context_t *ctx,
+ gs_function_t *func, int extend,
+ float x0, float y0, float r0,
+ float x1, float y1, float r1)
+{
+ gs_memory_t *mem = ctx->memory;
+ gs_shading_t *shading;
+ gs_shading_R_params_t params;
+ int code;
+
+ gs_shading_R_params_init(&params);
+ {
+ params.ColorSpace = ctx->srgb;
+
+ params.Coords[0] = x0;
+ params.Coords[1] = y0;
+ params.Coords[2] = r0;
+ params.Coords[3] = x1;
+ params.Coords[4] = y1;
+ params.Coords[5] = r1;
+
+ params.Extend[0] = extend;
+ params.Extend[1] = extend;
+
+ params.Function = func;
+ }
+
+ code = gs_shading_R_init(&shading, &params, mem);
+ if (code < 0)
+ return gs_throw(-1, "gs_shading_R_init failed");
+
+ gs_setsmoothness(ctx->pgs, 0.02);
+
+ code = gs_shfill(ctx->pgs, shading);
+ if (code < 0)
+ {
+ gs_free_object(mem, shading, "gs_shading_R");
+ return gs_throw(-1, "gs_shfill failed");
+ }
+
+ gs_free_object(mem, shading, "gs_shading_R");
+
+ return 0;
+}
+
+/*
+ * Linear gradients map to Axial shadings.
+ */
+
+static int
+xps_draw_one_linear_gradient(xps_context_t *ctx,
+ gs_function_t *func, int extend,
+ float x0, float y0, float x1, float y1)
+{
+ gs_memory_t *mem = ctx->memory;
+ gs_shading_t *shading;
+ gs_shading_A_params_t params;
+ int code;
+
+ gs_shading_A_params_init(&params);
+ {
+ params.ColorSpace = ctx->srgb;
+
+ params.Coords[0] = x0;
+ params.Coords[1] = y0;
+ params.Coords[2] = x1;
+ params.Coords[3] = y1;
+
+ params.Extend[0] = extend;
+ params.Extend[1] = extend;
+
+ params.Function = func;
+ }
+
+ code = gs_shading_A_init(&shading, &params, mem);
+ if (code < 0)
+ return gs_throw(-1, "gs_shading_A_init failed");
+
+ gs_setsmoothness(ctx->pgs, 0.02);
+
+ code = gs_shfill(ctx->pgs, shading);
+ if (code < 0)
+ {
+ gs_free_object(mem, shading, "gs_shading_A");
+ return gs_throw(-1, "gs_shfill failed");
+ }
+
+ gs_free_object(mem, shading, "gs_shading_A");
+
+ return 0;
+}
+
+/*
+ * We need to loop and create many shading objects to account
+ * for the Repeat and Reflect SpreadMethods.
+ * I'm not smart enough to calculate this analytically
+ * so we iterate and check each object until we
+ * reach a reasonable limit for infinite cases.
+ */
+
+static inline float point_inside_circle(float px, float py, float x, float y, float r)
+{
+ float dx = px - x;
+ float dy = py - y;
+ return (dx * dx + dy * dy) <= (r * r);
+}
+
+static int
+xps_draw_radial_gradient(xps_context_t *ctx, xps_item_t *root, int spread, gs_function_t *func)
+{
+ gs_rect bbox;
+ float x0, y0, r0;
+ float x1, y1, r1;
+ float xrad = 1;
+ float yrad = 1;
+ float invscale;
+ float dx, dy;
+ int code;
+ int i;
+ int done;
+
+ char *center_att = xps_att(root, "Center");
+ char *origin_att = xps_att(root, "GradientOrigin");
+ char *radius_x_att = xps_att(root, "RadiusX");
+ char *radius_y_att = xps_att(root, "RadiusY");
+
+ if (origin_att)
+ sscanf(origin_att, "%g,%g", &x0, &y0);
+ if (center_att)
+ sscanf(center_att, "%g,%g", &x1, &y1);
+ if (radius_x_att)
+ xrad = atof(radius_x_att);
+ if (radius_y_att)
+ yrad = atof(radius_y_att);
+
+ /* scale the ctm to make ellipses */
+ gs_scale(ctx->pgs, 1.0, yrad / xrad);
+
+ invscale = xrad / yrad;
+ y0 = y0 * invscale;
+ y1 = y1 * invscale;
+
+ r0 = 0.0;
+ r1 = xrad;
+
+ dx = x1 - x0;
+ dy = y1 - y0;
+
+ xps_bounds_in_user_space(ctx, &bbox);
+
+ if (spread == SPREAD_PAD)
+ {
+ if (!point_inside_circle(x0, y0, x1, y1, r1))
+ {
+ gs_function_t *reverse;
+ float in[1];
+ float out[4];
+ float fary[10];
+ void *vary[1];
+
+ /* PDF shadings with extend doesn't work the same way as XPS
+ * gradients when the radial shading is a cone. In this case
+ * we fill the background ourselves.
+ */
+
+ in[0] = 1.0;
+ out[0] = 1.0;
+ out[1] = 0.0;
+ out[2] = 0.0;
+ out[3] = 0.0;
+ if (ctx->opacity_only)
+ gs_function_evaluate(func, in, out);
+ else
+ gs_function_evaluate(func, in, out + 1);
+
+ xps_set_color(ctx, ctx->srgb, out);
+
+ gs_moveto(ctx->pgs, bbox.p.x, bbox.p.y);
+ gs_lineto(ctx->pgs, bbox.q.x, bbox.p.y);
+ gs_lineto(ctx->pgs, bbox.q.x, bbox.q.y);
+ gs_lineto(ctx->pgs, bbox.p.x, bbox.q.y);
+ gs_closepath(ctx->pgs);
+ gs_fill(ctx->pgs);
+
+ /* We also have to reverse the direction so the bigger circle
+ * comes first or the graphical results do not match. We also
+ * have to reverse the direction of the function to compensate.
+ */
+
+ reverse = xps_reverse_function(ctx, func, fary, vary);
+ code = xps_draw_one_radial_gradient(ctx, reverse, 1, x1, y1, r1, x0, y0, r0);
+ if (code < 0)
+ return gs_rethrow(code, "could not draw radial gradient");
+ xps_free(ctx, reverse);
+ }
+ else
+ {
+ code = xps_draw_one_radial_gradient(ctx, func, 1, x0, y0, r0, x1, y1, r1);
+ if (code < 0)
+ return gs_rethrow(code, "could not draw radial gradient");
+ }
+ }
+ else
+ {
+ for (i = 0; i < 100; i++)
+ {
+ /* Draw current circle */
+
+ if (!point_inside_circle(x0, y0, x1, y1, r1))
+ dputs("xps: we should reverse gradient here too\n");
+
+ if (spread == SPREAD_REFLECT && (i & 1))
+ code = xps_draw_one_radial_gradient(ctx, func, 0, x1, y1, r1, x0, y0, r0);
+ else
+ code = xps_draw_one_radial_gradient(ctx, func, 0, x0, y0, r0, x1, y1, r1);
+ if (code < 0)
+ return gs_rethrow(code, "could not draw axial gradient");
+
+ /* Check if circle encompassed the entire bounding box (break loop if we do) */
+
+ done = 1;
+ if (!point_inside_circle(bbox.p.x, bbox.p.y, x1, y1, r1)) done = 0;
+ if (!point_inside_circle(bbox.p.x, bbox.q.y, x1, y1, r1)) done = 0;
+ if (!point_inside_circle(bbox.q.x, bbox.q.y, x1, y1, r1)) done = 0;
+ if (!point_inside_circle(bbox.q.x, bbox.p.y, x1, y1, r1)) done = 0;
+ if (done)
+ break;
+
+ /* Prepare next circle */
+
+ r0 = r1;
+ r1 += xrad;
+
+ x0 += dx;
+ y0 += dy;
+ x1 += dx;
+ y1 += dy;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Calculate how many iterations are needed to cover
+ * the bounding box.
+ */
+
+static int
+xps_draw_linear_gradient(xps_context_t *ctx, xps_item_t *root, int spread, gs_function_t *func)
+{
+ gs_rect bbox;
+ float x0, y0, x1, y1;
+ float dx, dy;
+ int code;
+ int i;
+
+ char *start_point_att = xps_att(root, "StartPoint");
+ char *end_point_att = xps_att(root, "EndPoint");
+
+ x0 = 0;
+ y0 = 0;
+ x1 = 0;
+ y1 = 1;
+
+ if (start_point_att)
+ sscanf(start_point_att, "%g,%g", &x0, &y0);
+ if (end_point_att)
+ sscanf(end_point_att, "%g,%g", &x1, &y1);
+
+ dx = x1 - x0;
+ dy = y1 - y0;
+
+ xps_bounds_in_user_space(ctx, &bbox);
+
+ if (spread == SPREAD_PAD)
+ {
+ code = xps_draw_one_linear_gradient(ctx, func, 1, x0, y0, x1, y1);
+ if (code < 0)
+ return gs_rethrow(code, "could not draw axial gradient");
+ }
+ else
+ {
+ float len;
+ float a, b;
+ float dist[4];
+ float d0, d1;
+ int i0, i1;
+
+ len = sqrt(dx * dx + dy * dy);
+ a = dx / len;
+ b = dy / len;
+
+ dist[0] = a * (bbox.p.x - x0) + b * (bbox.p.y - y0);
+ dist[1] = a * (bbox.p.x - x0) + b * (bbox.q.y - y0);
+ dist[2] = a * (bbox.q.x - x0) + b * (bbox.q.y - y0);
+ dist[3] = a * (bbox.q.x - x0) + b * (bbox.p.y - y0);
+
+ d0 = dist[0];
+ d1 = dist[0];
+ for (i = 1; i < 4; i++)
+ {
+ if (dist[i] < d0) d0 = dist[i];
+ if (dist[i] > d1) d1 = dist[i];
+ }
+
+ i0 = floor(d0 / len);
+ i1 = ceil(d1 / len);
+
+ for (i = i0; i < i1; i++)
+ {
+ if (spread == SPREAD_REFLECT && (i & 1))
+ {
+ code = xps_draw_one_linear_gradient(ctx, func, 0,
+ x1 + dx * i, y1 + dy * i,
+ x0 + dx * i, y0 + dy * i);
+ }
+ else
+ {
+ code = xps_draw_one_linear_gradient(ctx, func, 0,
+ x0 + dx * i, y0 + dy * i,
+ x1 + dx * i, y1 + dy * i);
+ }
+ if (code < 0)
+ return gs_rethrow(code, "could not draw axial gradient");
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Parse XML tag and attributes for a gradient brush, create color/opacity
+ * function objects and call gradient drawing primitives.
+ */
+
+static int
+xps_parse_gradient_brush(xps_context_t *ctx, xps_resource_t *dict, xps_item_t *root,
+ int (*draw)(xps_context_t *, xps_item_t *, int, gs_function_t *))
+{
+ xps_item_t *node;
+
+ char *opacity_att;
+ char *interpolation_att;
+ char *spread_att;
+ char *mapping_att;
+ char *transform_att;
+
+ xps_item_t *transform_tag = NULL;
+ xps_item_t *stop_tag = NULL;
+
+ float stop_offsets[MAX_STOPS];
+ float stop_colors[MAX_STOPS * 4];
+ int stop_count;
+ gs_matrix transform;
+ int spread_method;
+
+ gs_rect saved_bounds;
+ gs_rect bbox;
+
+ gs_function_t *color_func;
+ gs_function_t *opacity_func;
+ int has_opacity = 0;
+
+ opacity_att = xps_att(root, "Opacity");
+ interpolation_att = xps_att(root, "ColorInterpolationMode");
+ spread_att = xps_att(root, "SpreadMethod");
+ mapping_att = xps_att(root, "MappingMode");
+ transform_att = xps_att(root, "Transform");
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "LinearGradientBrush.Transform"))
+ transform_tag = xps_down(node);
+ if (!strcmp(xps_tag(node), "RadialGradientBrush.Transform"))
+ transform_tag = xps_down(node);
+ if (!strcmp(xps_tag(node), "LinearGradientBrush.GradientStops"))
+ stop_tag = xps_down(node);
+ if (!strcmp(xps_tag(node), "RadialGradientBrush.GradientStops"))
+ stop_tag = xps_down(node);
+ }
+
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag);
+
+ spread_method = SPREAD_PAD;
+ if (spread_att)
+ {
+ if (!strcmp(spread_att, "Pad"))
+ spread_method = SPREAD_PAD;
+ if (!strcmp(spread_att, "Reflect"))
+ spread_method = SPREAD_REFLECT;
+ if (!strcmp(spread_att, "Repeat"))
+ spread_method = SPREAD_REPEAT;
+ }
+
+ gs_make_identity(&transform);
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+
+ if (!stop_tag)
+ return gs_throw(-1, "missing gradient stops tag");
+
+ stop_count = xps_parse_gradient_stops(ctx, stop_tag, stop_offsets, stop_colors, MAX_STOPS);
+ if (stop_count == 0)
+ return gs_throw(-1, "no gradient stops found");
+
+ color_func = xps_create_gradient_stop_function(ctx, stop_offsets, stop_colors, stop_count, 0);
+ if (!color_func)
+ return gs_rethrow(-1, "could not create color gradient function");
+
+ opacity_func = xps_create_gradient_stop_function(ctx, stop_offsets, stop_colors, stop_count, 1);
+ if (!opacity_func)
+ return gs_rethrow(-1, "could not create opacity gradient function");
+
+ has_opacity = xps_gradient_has_transparent_colors(stop_offsets, stop_colors, stop_count);
+
+ xps_clip(ctx, &saved_bounds);
+
+ gs_gsave(ctx->pgs);
+
+ gs_concat(ctx->pgs, &transform);
+
+ xps_bounds_in_user_space(ctx, &bbox);
+
+ xps_begin_opacity(ctx, dict, opacity_att, NULL);
+
+ if (ctx->opacity_only)
+ {
+ draw(ctx, root, spread_method, opacity_func);
+ }
+ else
+ {
+ if (has_opacity)
+ {
+ gs_transparency_mask_params_t params;
+ gs_transparency_group_params_t tgp;
+
+ gs_trans_mask_params_init(&params, TRANSPARENCY_MASK_Alpha);
+ gs_begin_transparency_mask(ctx->pgs, &params, &bbox, 0);
+ draw(ctx, root, spread_method, opacity_func);
+ gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
+
+ gs_trans_group_params_init(&tgp);
+ gs_begin_transparency_group(ctx->pgs, &tgp, &bbox);
+ draw(ctx, root, spread_method, color_func);
+ gs_end_transparency_group(ctx->pgs);
+ }
+ else
+ {
+ draw(ctx, root, spread_method, color_func);
+ }
+ }
+
+ xps_end_opacity(ctx, dict, opacity_att, NULL);
+
+ gs_grestore(ctx->pgs);
+
+ xps_unclip(ctx, &saved_bounds);
+
+ xps_free_gradient_stop_function(ctx, opacity_func);
+ xps_free_gradient_stop_function(ctx, color_func);
+
+ return 0;
+}
+
+int
+xps_parse_linear_gradient_brush(xps_context_t *ctx, xps_resource_t *dict, xps_item_t *root)
+{
+ return xps_parse_gradient_brush(ctx, dict, root, xps_draw_linear_gradient);
+}
+
+int
+xps_parse_radial_gradient_brush(xps_context_t *ctx, xps_resource_t *dict, xps_item_t *root)
+{
+ return xps_parse_gradient_brush(ctx, dict, root, xps_draw_radial_gradient);
+}
+