Example #1
0
static void
xps_paint_tiling_brush_clipped(xps_document *doc, const fz_matrix *ctm, const fz_rect *viewbox, struct closure *c)
{
	fz_path *path = fz_new_path(doc->ctx);
	fz_moveto(doc->ctx, path, viewbox->x0, viewbox->y0);
	fz_lineto(doc->ctx, path, viewbox->x0, viewbox->y1);
	fz_lineto(doc->ctx, path, viewbox->x1, viewbox->y1);
	fz_lineto(doc->ctx, path, viewbox->x1, viewbox->y0);
	fz_closepath(doc->ctx, path);
	fz_clip_path(doc->dev, path, NULL, 0, ctm);
	fz_free_path(doc->ctx, path);
	c->func(doc, ctm, viewbox, c->base_uri, c->dict, c->root, c->user);
	fz_pop_clip(doc->dev);
}
Example #2
0
static void
xps_paint_tiling_brush_clipped(xps_context *ctx, fz_matrix ctm, fz_rect viewbox, struct closure *c)
{
	fz_path *path = fz_new_path();
	fz_moveto(path, viewbox.x0, viewbox.y0);
	fz_lineto(path, viewbox.x0, viewbox.y1);
	fz_lineto(path, viewbox.x1, viewbox.y1);
	fz_lineto(path, viewbox.x1, viewbox.y0);
	fz_closepath(path);
	fz_clip_path(ctx->dev, path, NULL, 0, ctm);
	fz_free_path(path);
	c->func(ctx, ctm, viewbox, c->base_uri, c->dict, c->root, c->user);
	fz_pop_clip(ctx->dev);
}
void
xps_clip(xps_document *doc, const fz_matrix *ctm, xps_resource *dict, char *clip_att, fz_xml *clip_tag)
{
	fz_path *path;
	int fill_rule = 0;

	if (clip_att)
		path = xps_parse_abbreviated_geometry(doc, clip_att, &fill_rule);
	else if (clip_tag)
		path = xps_parse_path_geometry(doc, dict, clip_tag, 0, &fill_rule);
	else
		path = fz_new_path(doc->ctx);
	fz_clip_path(doc->dev, path, NULL, fill_rule == 0, ctm);
	fz_free_path(doc->ctx, path);
}
Example #4
0
static void
xps_paint_tiling_brush_clipped(xps_document *doc, const fz_matrix *ctm, const fz_rect *viewbox, struct closure *c)
{
	fz_path *path = fz_new_path(doc->ctx);
	fz_rect rect;
	fz_moveto(doc->ctx, path, viewbox->x0, viewbox->y0);
	fz_lineto(doc->ctx, path, viewbox->x0, viewbox->y1);
	fz_lineto(doc->ctx, path, viewbox->x1, viewbox->y1);
	fz_lineto(doc->ctx, path, viewbox->x1, viewbox->y0);
	fz_closepath(doc->ctx, path);
	/* SumatraPDF: try to match rendering with and without display list */
	fz_clip_path(doc->dev, path, fz_bound_path(doc->ctx, path, NULL, ctm, &rect), 0, ctm);
	fz_free_path(doc->ctx, path);
	c->func(doc, ctm, viewbox, c->base_uri, c->dict, c->root, c->user);
	fz_pop_clip(doc->dev);
}
Example #5
0
static void
fz_free_display_node(fz_context *ctx, fz_display_node *node)
{
	switch (node->cmd)
	{
	case FZ_CMD_FILL_PATH:
	case FZ_CMD_STROKE_PATH:
	case FZ_CMD_CLIP_PATH:
	case FZ_CMD_CLIP_STROKE_PATH:
		fz_free_path(ctx, node->item.path);
		break;
	case FZ_CMD_FILL_TEXT:
	case FZ_CMD_STROKE_TEXT:
	case FZ_CMD_CLIP_TEXT:
	case FZ_CMD_CLIP_STROKE_TEXT:
	case FZ_CMD_IGNORE_TEXT:
		fz_free_text(ctx, node->item.text);
		break;
	case FZ_CMD_FILL_SHADE:
		fz_drop_shade(ctx, node->item.shade);
		break;
	case FZ_CMD_FILL_IMAGE:
	case FZ_CMD_FILL_IMAGE_MASK:
	case FZ_CMD_CLIP_IMAGE_MASK:
		fz_drop_image(ctx, node->item.image);
		break;
	case FZ_CMD_POP_CLIP:
	case FZ_CMD_BEGIN_MASK:
	case FZ_CMD_END_MASK:
	case FZ_CMD_BEGIN_GROUP:
	case FZ_CMD_END_GROUP:
	case FZ_CMD_BEGIN_TILE:
	case FZ_CMD_END_TILE:
	case FZ_CMD_BEGIN_PAGE:
	case FZ_CMD_END_PAGE:
		break;
	/* SumatraPDF: support transfer functions */
	case FZ_CMD_APPLY_TRANSFER_FUNCTION:
		fz_drop_transfer_function(ctx, node->item.tr);
		break;
	}
	if (node->stroke)
		fz_drop_stroke_state(ctx, node->stroke);
	if (node->colorspace)
		fz_drop_colorspace(ctx, node->colorspace);
	fz_free(ctx, node);
}
Example #6
0
void
xps_clip(xps_document *doc, const fz_matrix *ctm, xps_resource *dict, char *clip_att, fz_xml *clip_tag)
{
	fz_path *path;
	int fill_rule = 0;
	fz_rect rect;

	if (clip_att)
		path = xps_parse_abbreviated_geometry(doc, clip_att, &fill_rule);
	else if (clip_tag)
		path = xps_parse_path_geometry(doc, dict, clip_tag, 0, &fill_rule);
	else
		path = fz_new_path(doc->ctx);
	/* SumatraPDF: try to match rendering with and without display list */
	fz_clip_path(doc->dev, path, fz_bound_path(doc->ctx, path, NULL, ctm, &rect), fill_rule == 0, ctm);
	fz_free_path(doc->ctx, path);
}
Example #7
0
static void
fz_free_display_node(fz_display_node *node)
{
	switch (node->cmd)
	{
	case FZ_CMD_FILL_PATH:
	case FZ_CMD_STROKE_PATH:
	case FZ_CMD_CLIP_PATH:
	case FZ_CMD_CLIP_STROKE_PATH:
		fz_free_path(node->item.path);
		break;
	case FZ_CMD_FILL_TEXT:
	case FZ_CMD_STROKE_TEXT:
	case FZ_CMD_CLIP_TEXT:
	case FZ_CMD_CLIP_STROKE_TEXT:
	case FZ_CMD_IGNORE_TEXT:
		fz_free_text(node->item.text);
		break;
	case FZ_CMD_FILL_SHADE:
		fz_drop_shade(node->item.shade);
		break;
	case FZ_CMD_FILL_IMAGE:
	case FZ_CMD_FILL_IMAGE_MASK:
	case FZ_CMD_CLIP_IMAGE_MASK:
		fz_drop_pixmap(node->item.image);
		break;
	case FZ_CMD_POP_CLIP:
	case FZ_CMD_BEGIN_MASK:
	case FZ_CMD_END_MASK:
	case FZ_CMD_BEGIN_GROUP:
	case FZ_CMD_END_GROUP:
	case FZ_CMD_BEGIN_TILE:
	case FZ_CMD_END_TILE:
		break;
	}
	if (node->stroke)
		fz_free(node->stroke);
	if (node->colorspace)
		fz_drop_colorspace(node->colorspace);
	fz_free(node);
}
void
xps_parse_path(xps_document *doc, const fz_matrix *ctm, char *base_uri, xps_resource *dict, fz_xml *root)
{
	fz_xml *node;

	char *fill_uri;
	char *stroke_uri;
	char *opacity_mask_uri;

	char *transform_att;
	char *clip_att;
	char *data_att;
	char *fill_att;
	char *stroke_att;
	char *opacity_att;
	char *opacity_mask_att;

	fz_xml *transform_tag = NULL;
	fz_xml *clip_tag = NULL;
	fz_xml *data_tag = NULL;
	fz_xml *fill_tag = NULL;
	fz_xml *stroke_tag = NULL;
	fz_xml *opacity_mask_tag = NULL;

	char *fill_opacity_att = NULL;
	char *stroke_opacity_att = NULL;

	char *stroke_dash_array_att;
	char *stroke_dash_cap_att;
	char *stroke_dash_offset_att;
	char *stroke_end_line_cap_att;
	char *stroke_start_line_cap_att;
	char *stroke_line_join_att;
	char *stroke_miter_limit_att;
	char *stroke_thickness_att;
	char *navigate_uri_att;

	fz_stroke_state *stroke = NULL;
	fz_matrix transform;
	float samples[32];
	fz_colorspace *colorspace;
	fz_path *path = NULL;
	fz_path *stroke_path = NULL;
	fz_rect area;
	int fill_rule;
	int dash_len = 0;
	fz_matrix local_ctm;

	/*
	 * Extract attributes and extended attributes.
	 */

	transform_att = fz_xml_att(root, "RenderTransform");
	clip_att = fz_xml_att(root, "Clip");
	data_att = fz_xml_att(root, "Data");
	fill_att = fz_xml_att(root, "Fill");
	stroke_att = fz_xml_att(root, "Stroke");
	opacity_att = fz_xml_att(root, "Opacity");
	opacity_mask_att = fz_xml_att(root, "OpacityMask");

	stroke_dash_array_att = fz_xml_att(root, "StrokeDashArray");
	stroke_dash_cap_att = fz_xml_att(root, "StrokeDashCap");
	stroke_dash_offset_att = fz_xml_att(root, "StrokeDashOffset");
	stroke_end_line_cap_att = fz_xml_att(root, "StrokeEndLineCap");
	stroke_start_line_cap_att = fz_xml_att(root, "StrokeStartLineCap");
	stroke_line_join_att = fz_xml_att(root, "StrokeLineJoin");
	stroke_miter_limit_att = fz_xml_att(root, "StrokeMiterLimit");
	stroke_thickness_att = fz_xml_att(root, "StrokeThickness");
	navigate_uri_att = fz_xml_att(root, "FixedPage.NavigateUri");

	for (node = fz_xml_down(root); node; node = fz_xml_next(node))
	{
		if (!strcmp(fz_xml_tag(node), "Path.RenderTransform"))
			transform_tag = fz_xml_down(node);
		if (!strcmp(fz_xml_tag(node), "Path.OpacityMask"))
			opacity_mask_tag = fz_xml_down(node);
		if (!strcmp(fz_xml_tag(node), "Path.Clip"))
			clip_tag = fz_xml_down(node);
		if (!strcmp(fz_xml_tag(node), "Path.Fill"))
			fill_tag = fz_xml_down(node);
		if (!strcmp(fz_xml_tag(node), "Path.Stroke"))
			stroke_tag = fz_xml_down(node);
		if (!strcmp(fz_xml_tag(node), "Path.Data"))
			data_tag = fz_xml_down(node);
	}

	fill_uri = base_uri;
	stroke_uri = base_uri;
	opacity_mask_uri = base_uri;

	xps_resolve_resource_reference(doc, dict, &data_att, &data_tag, NULL);
	xps_resolve_resource_reference(doc, dict, &clip_att, &clip_tag, NULL);
	xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
	xps_resolve_resource_reference(doc, dict, &fill_att, &fill_tag, &fill_uri);
	xps_resolve_resource_reference(doc, dict, &stroke_att, &stroke_tag, &stroke_uri);
	xps_resolve_resource_reference(doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);

	/*
	 * Act on the information we have gathered:
	 */

	if (!data_att && !data_tag)
		return;

	if (fill_tag && !strcmp(fz_xml_tag(fill_tag), "SolidColorBrush"))
	{
		fill_opacity_att = fz_xml_att(fill_tag, "Opacity");
		fill_att = fz_xml_att(fill_tag, "Color");
		fill_tag = NULL;
	}

	if (stroke_tag && !strcmp(fz_xml_tag(stroke_tag), "SolidColorBrush"))
	{
		stroke_opacity_att = fz_xml_att(stroke_tag, "Opacity");
		stroke_att = fz_xml_att(stroke_tag, "Color");
		stroke_tag = NULL;
	}

	if (stroke_att || stroke_tag)
	{
		if (stroke_dash_array_att)
		{
			char *s = stroke_dash_array_att;

			while (*s)
			{
				while (*s == ' ')
					s++;
				if (*s) /* needed in case of a space before the last quote */
					dash_len++;

				while (*s && *s != ' ')
					s++;
			}
		}
		stroke = fz_new_stroke_state_with_len(doc->ctx, dash_len);
		stroke->start_cap = xps_parse_line_cap(stroke_start_line_cap_att);
		stroke->dash_cap = xps_parse_line_cap(stroke_dash_cap_att);
		stroke->end_cap = xps_parse_line_cap(stroke_end_line_cap_att);

		stroke->linejoin = FZ_LINEJOIN_MITER_XPS;
		if (stroke_line_join_att)
		{
			if (!strcmp(stroke_line_join_att, "Miter")) stroke->linejoin = FZ_LINEJOIN_MITER_XPS;
			if (!strcmp(stroke_line_join_att, "Round")) stroke->linejoin = FZ_LINEJOIN_ROUND;
			if (!strcmp(stroke_line_join_att, "Bevel")) stroke->linejoin = FZ_LINEJOIN_BEVEL;
		}

		stroke->miterlimit = 10;
		if (stroke_miter_limit_att)
			stroke->miterlimit = fz_atof(stroke_miter_limit_att);

		stroke->linewidth = 1;
		if (stroke_thickness_att)
			stroke->linewidth = fz_atof(stroke_thickness_att);

		stroke->dash_phase = 0;
		stroke->dash_len = 0;
		if (stroke_dash_array_att)
		{
			char *s = stroke_dash_array_att;

			if (stroke_dash_offset_att)
				stroke->dash_phase = fz_atof(stroke_dash_offset_att) * stroke->linewidth;

			while (*s)
			{
				while (*s == ' ')
					s++;
				if (*s) /* needed in case of a space before the last quote */
					stroke->dash_list[stroke->dash_len++] = fz_atof(s) * stroke->linewidth;
				while (*s && *s != ' ')
					s++;
			}
			/* cf. https://code.google.com/p/sumatrapdf/issues/detail?id=2339 */
			if (dash_len > 0)
			{
				float phase_len = 0.0f;
				int i;
				for (i = 0; i < dash_len; i++)
					phase_len += stroke->dash_list[i];
				if (phase_len == 0.0f)
					dash_len = 0;
			}
			stroke->dash_len = dash_len;
		}
	}

	transform = fz_identity;
	if (transform_att)
		xps_parse_render_transform(doc, transform_att, &transform);
	if (transform_tag)
		xps_parse_matrix_transform(doc, transform_tag, &transform);
	fz_concat(&local_ctm, &transform, ctm);

	if (clip_att || clip_tag)
		xps_clip(doc, &local_ctm, dict, clip_att, clip_tag);

	fill_rule = 0;
	if (data_att)
		path = xps_parse_abbreviated_geometry(doc, data_att, &fill_rule);
	else if (data_tag)
	{
		path = xps_parse_path_geometry(doc, dict, data_tag, 0, &fill_rule);
		if (stroke_att || stroke_tag)
			stroke_path = xps_parse_path_geometry(doc, dict, data_tag, 1, &fill_rule);
	}
	if (!stroke_path)
		stroke_path = path;

	if (stroke_att || stroke_tag)
	{
		fz_bound_path(doc->ctx, stroke_path, stroke, &local_ctm, &area);
		if (stroke_path != path && (fill_att || fill_tag)) {
			fz_rect bounds;
			fz_bound_path(doc->ctx, path, NULL, &local_ctm, &bounds);
			fz_union_rect(&area, &bounds);
		}
	}
	else
		fz_bound_path(doc->ctx, path, NULL, &local_ctm, &area);

	/* SumatraPDF: extended link support */
	xps_extract_anchor_info(doc, &area, navigate_uri_att, fz_xml_att(root, "Name"), 0);
	navigate_uri_att = NULL;

	if (navigate_uri_att)
		xps_add_link(doc, &area, base_uri, navigate_uri_att);

	xps_begin_opacity(doc, &local_ctm, &area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);

	if (fill_att)
	{
		xps_parse_color(doc, base_uri, fill_att, &colorspace, samples);
		if (fill_opacity_att)
			samples[0] *= fz_atof(fill_opacity_att);
		xps_set_color(doc, colorspace, samples);

		fz_fill_path(doc->dev, path, fill_rule == 0, &local_ctm,
			doc->colorspace, doc->color, doc->alpha);
	}

	if (fill_tag)
	{
		fz_clip_path(doc->dev, path, NULL, fill_rule == 0, &local_ctm);
		xps_parse_brush(doc, &local_ctm, &area, fill_uri, dict, fill_tag);
		fz_pop_clip(doc->dev);
	}

	if (stroke_att)
	{
		xps_parse_color(doc, base_uri, stroke_att, &colorspace, samples);
		if (stroke_opacity_att)
			samples[0] *= fz_atof(stroke_opacity_att);
		xps_set_color(doc, colorspace, samples);

		fz_stroke_path(doc->dev, stroke_path, stroke, &local_ctm,
			doc->colorspace, doc->color, doc->alpha);
	}

	if (stroke_tag)
	{
		fz_clip_stroke_path(doc->dev, stroke_path, NULL, stroke, &local_ctm);
		xps_parse_brush(doc, &local_ctm, &area, stroke_uri, dict, stroke_tag);
		fz_pop_clip(doc->dev);
	}

	xps_end_opacity(doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);

	if (stroke_path != path)
		fz_free_path(doc->ctx, stroke_path);
	fz_free_path(doc->ctx, path);
	path = NULL;
	fz_drop_stroke_state(doc->ctx, stroke);

	if (clip_att || clip_tag)
		fz_pop_clip(doc->dev);
}
Example #9
0
static void
fz_draw_clip_text(fz_device *devp, fz_text *text, fz_matrix ctm, int accumulate)
{
	fz_draw_device *dev = devp->user;
	fz_context *ctx = dev->ctx;
	fz_bbox bbox;
	fz_pixmap *mask, *dest, *shape;
	fz_matrix tm, trm, trunc_trm;
	fz_pixmap *glyph;
	int i, x, y, gid;
	fz_draw_state *state;
	fz_colorspace *model;

	/* If accumulate == 0 then this text object is guaranteed complete */
	/* If accumulate == 1 then this text object is the first (or only) in a sequence */
	/* If accumulate == 2 then this text object is a continuation */

	state = push_stack(dev);
	model = state->dest->colorspace;

	if (accumulate == 0)
	{
		/* make the mask the exact size needed */
		bbox = fz_bbox_covering_rect(fz_bound_text(dev->ctx, text, ctm));
		bbox = fz_intersect_bbox(bbox, state->scissor);
	}
	else
	{
		/* be conservative about the size of the mask needed */
		bbox = state->scissor;
	}

	if (accumulate == 0 || accumulate == 1)
	{
		mask = fz_new_pixmap_with_bbox(dev->ctx, NULL, bbox);
		fz_clear_pixmap(dev->ctx, mask);
		dest = fz_new_pixmap_with_bbox(dev->ctx, model, bbox);
		fz_clear_pixmap(dev->ctx, dest);
		if (state->shape)
		{
			shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, bbox);
			fz_clear_pixmap(dev->ctx, shape);
		}
		else
			shape = NULL;

		state[1].blendmode |= FZ_BLEND_ISOLATED;
		state[1].scissor = bbox;
		state[1].dest = dest;
		state[1].mask = mask;
		state[1].shape = shape;
#ifdef DUMP_GROUP_BLENDS
		dump_spaces(dev->top-1, "Clip (text) begin\n");
#endif
	}
	else
	{
		mask = state->mask;
		dev->top--;
	}

	if (!fz_is_empty_rect(bbox) && mask)
	{
		tm = text->trm;

		for (i = 0; i < text->len; i++)
		{
			gid = text->items[i].gid;
			if (gid < 0)
				continue;

			tm.e = text->items[i].x;
			tm.f = text->items[i].y;
			trm = fz_concat(tm, ctm);
			x = floorf(trm.e);
			y = floorf(trm.f);

			trunc_trm = trm;
			trunc_trm.e = QUANT(trm.e - floorf(trm.e), HSUBPIX);
			trunc_trm.f = QUANT(trm.f - floorf(trm.f), VSUBPIX);

			glyph = fz_render_glyph(dev->ctx, text->font, gid, trunc_trm, model, bbox);
			if (glyph)
			{
				draw_glyph(NULL, mask, glyph, x, y, bbox);
				if (state[1].shape)
					draw_glyph(NULL, state[1].shape, glyph, x, y, bbox);
				fz_drop_pixmap(dev->ctx, glyph);
			}
			else
			{
				fz_path *path = fz_outline_glyph(dev->ctx, text->font, gid, trm);
				if (path)
				{
					fz_pixmap *old_dest;
					float white = 1;

					state = &dev->stack[dev->top];
					old_dest = state[0].dest;
					state[0].dest = state[0].mask;
					state[0].mask = NULL;
					fz_try(ctx)
					{
						fz_draw_fill_path(devp, path, 0, fz_identity, fz_device_gray, &white, 1);
					}
					fz_always(ctx)
					{
						state[0].mask = state[0].dest;
						state[0].dest = old_dest;
						fz_free_path(dev->ctx, path);
					}
					fz_catch(ctx)
					{
						fz_rethrow(ctx);
					}
				}
				else
				{
					fz_warn(dev->ctx, "cannot render glyph for clipping");
				}
			}
		}
Example #10
0
static void
fz_draw_stroke_text(fz_device *devp, fz_text *text, fz_stroke_state *stroke, fz_matrix ctm,
	fz_colorspace *colorspace, float *color, float alpha)
{
	fz_draw_device *dev = devp->user;
	unsigned char colorbv[FZ_MAX_COLORS + 1];
	float colorfv[FZ_MAX_COLORS];
	fz_matrix tm, trm, trunc_trm;
	fz_pixmap *glyph;
	int i, x, y, gid;
	fz_draw_state *state = &dev->stack[dev->top];
	fz_colorspace *model = state->dest->colorspace;
	fz_bbox scissor = state->scissor;

	if (state->blendmode & FZ_BLEND_KNOCKOUT)
		state = fz_knockout_begin(dev);

	fz_convert_color(dev->ctx, model, colorfv, colorspace, color);
	for (i = 0; i < model->n; i++)
		colorbv[i] = colorfv[i] * 255;
	colorbv[i] = alpha * 255;

	tm = text->trm;

	for (i = 0; i < text->len; i++)
	{
		gid = text->items[i].gid;
		if (gid < 0)
			continue;

		tm.e = text->items[i].x;
		tm.f = text->items[i].y;
		trm = fz_concat(tm, ctm);
		x = floorf(trm.e);
		y = floorf(trm.f);

		trunc_trm = trm;
		trunc_trm.e = QUANT(trm.e - floorf(trm.e), HSUBPIX);
		trunc_trm.f = QUANT(trm.f - floorf(trm.f), VSUBPIX);

		scissor.x0 -= x; scissor.x1 -= x;
		scissor.y0 -= y; scissor.y1 -= y;

		glyph = fz_render_stroked_glyph(dev->ctx, text->font, gid, trunc_trm, ctm, stroke, scissor);
		if (glyph)
		{
			draw_glyph(colorbv, state->dest, glyph, x, y, state->scissor);
			if (state->shape)
				draw_glyph(colorbv, state->shape, glyph, x, y, state->scissor);
			fz_drop_pixmap(dev->ctx, glyph);
		}
		else
		{
			fz_path *path = fz_outline_glyph(dev->ctx, text->font, gid, trm);
			if (path)
			{
				fz_draw_stroke_path(devp, path, stroke, fz_identity, colorspace, color, alpha);
				fz_free_path(dev->ctx, path);
			}
			else
			{
				fz_warn(dev->ctx, "cannot render glyph");
			}
		}
	}

	if (state->blendmode & FZ_BLEND_KNOCKOUT)
		fz_knockout_end(dev);
}
Example #11
0
void
xps_parse_path(xps_document *doc, fz_matrix ctm, char *base_uri, xps_resource *dict, xml_element *root)
{
    xml_element *node;

    char *fill_uri;
    char *stroke_uri;
    char *opacity_mask_uri;

    char *transform_att;
    char *clip_att;
    char *data_att;
    char *fill_att;
    char *stroke_att;
    char *opacity_att;
    char *opacity_mask_att;

    xml_element *transform_tag = NULL;
    xml_element *clip_tag = NULL;
    xml_element *data_tag = NULL;
    xml_element *fill_tag = NULL;
    xml_element *stroke_tag = NULL;
    xml_element *opacity_mask_tag = NULL;

    char *fill_opacity_att = NULL;
    char *stroke_opacity_att = NULL;

    char *stroke_dash_array_att;
    char *stroke_dash_cap_att;
    char *stroke_dash_offset_att;
    char *stroke_end_line_cap_att;
    char *stroke_start_line_cap_att;
    char *stroke_line_join_att;
    char *stroke_miter_limit_att;
    char *stroke_thickness_att;
    char *navigate_uri_att;

    fz_stroke_state stroke;
    fz_matrix transform;
    float samples[32];
    fz_colorspace *colorspace;
    fz_path *path;
    fz_rect area;
    int fill_rule;

    /*
     * Extract attributes and extended attributes.
     */

    transform_att = xml_att(root, "RenderTransform");
    clip_att = xml_att(root, "Clip");
    data_att = xml_att(root, "Data");
    fill_att = xml_att(root, "Fill");
    stroke_att = xml_att(root, "Stroke");
    opacity_att = xml_att(root, "Opacity");
    opacity_mask_att = xml_att(root, "OpacityMask");

    stroke_dash_array_att = xml_att(root, "StrokeDashArray");
    stroke_dash_cap_att = xml_att(root, "StrokeDashCap");
    stroke_dash_offset_att = xml_att(root, "StrokeDashOffset");
    stroke_end_line_cap_att = xml_att(root, "StrokeEndLineCap");
    stroke_start_line_cap_att = xml_att(root, "StrokeStartLineCap");
    stroke_line_join_att = xml_att(root, "StrokeLineJoin");
    stroke_miter_limit_att = xml_att(root, "StrokeMiterLimit");
    stroke_thickness_att = xml_att(root, "StrokeThickness");
    navigate_uri_att = xml_att(root, "FixedPage.NavigateUri");

    for (node = xml_down(root); node; node = xml_next(node))
    {
        if (!strcmp(xml_tag(node), "Path.RenderTransform"))
            transform_tag = xml_down(node);
        if (!strcmp(xml_tag(node), "Path.OpacityMask"))
            opacity_mask_tag = xml_down(node);
        if (!strcmp(xml_tag(node), "Path.Clip"))
            clip_tag = xml_down(node);
        if (!strcmp(xml_tag(node), "Path.Fill"))
            fill_tag = xml_down(node);
        if (!strcmp(xml_tag(node), "Path.Stroke"))
            stroke_tag = xml_down(node);
        if (!strcmp(xml_tag(node), "Path.Data"))
            data_tag = xml_down(node);
    }

    fill_uri = base_uri;
    stroke_uri = base_uri;
    opacity_mask_uri = base_uri;

    xps_resolve_resource_reference(doc, dict, &data_att, &data_tag, NULL);
    xps_resolve_resource_reference(doc, dict, &clip_att, &clip_tag, NULL);
    xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
    xps_resolve_resource_reference(doc, dict, &fill_att, &fill_tag, &fill_uri);
    xps_resolve_resource_reference(doc, dict, &stroke_att, &stroke_tag, &stroke_uri);
    xps_resolve_resource_reference(doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);

    /*
     * Act on the information we have gathered:
     */

    if (!data_att && !data_tag)
        return;

    if (fill_tag && !strcmp(xml_tag(fill_tag), "SolidColorBrush"))
    {
        fill_opacity_att = xml_att(fill_tag, "Opacity");
        fill_att = xml_att(fill_tag, "Color");
        fill_tag = NULL;
    }

    if (stroke_tag && !strcmp(xml_tag(stroke_tag), "SolidColorBrush"))
    {
        stroke_opacity_att = xml_att(stroke_tag, "Opacity");
        stroke_att = xml_att(stroke_tag, "Color");
        stroke_tag = NULL;
    }

    stroke.start_cap = xps_parse_line_cap(stroke_start_line_cap_att);
    stroke.dash_cap = xps_parse_line_cap(stroke_dash_cap_att);
    stroke.end_cap = xps_parse_line_cap(stroke_end_line_cap_att);

    stroke.linejoin = FZ_LINEJOIN_MITER_XPS;
    if (stroke_line_join_att)
    {
        if (!strcmp(stroke_line_join_att, "Miter")) stroke.linejoin = FZ_LINEJOIN_MITER_XPS;
        if (!strcmp(stroke_line_join_att, "Round")) stroke.linejoin = FZ_LINEJOIN_ROUND;
        if (!strcmp(stroke_line_join_att, "Bevel")) stroke.linejoin = FZ_LINEJOIN_BEVEL;
    }

    stroke.miterlimit = 10;
    if (stroke_miter_limit_att)
        stroke.miterlimit = fz_atof(stroke_miter_limit_att);

    stroke.linewidth = 1;
    if (stroke_thickness_att)
        stroke.linewidth = fz_atof(stroke_thickness_att);

    stroke.dash_phase = 0;
    stroke.dash_len = 0;
    if (stroke_dash_array_att)
    {
        char *s = stroke_dash_array_att;

        if (stroke_dash_offset_att)
            stroke.dash_phase = fz_atof(stroke_dash_offset_att) * stroke.linewidth;

        while (*s && stroke.dash_len < nelem(stroke.dash_list))
        {
            while (*s == ' ')
                s++;
            if (*s) /* needed in case of a space before the last quote */
                stroke.dash_list[stroke.dash_len++] = fz_atof(s) * stroke.linewidth;
            while (*s && *s != ' ')
                s++;
        }
    }

    transform = fz_identity;
    if (transform_att)
        xps_parse_render_transform(doc, transform_att, &transform);
    if (transform_tag)
        xps_parse_matrix_transform(doc, transform_tag, &transform);
    ctm = fz_concat(transform, ctm);

    if (clip_att || clip_tag)
        xps_clip(doc, ctm, dict, clip_att, clip_tag);

    fill_rule = 0;
    if (data_att)
        path = xps_parse_abbreviated_geometry(doc, data_att, &fill_rule);
    else if (data_tag)
        path = xps_parse_path_geometry(doc, dict, data_tag, 0, &fill_rule);

    if (stroke_att || stroke_tag)
        area = fz_bound_path(doc->ctx, path, &stroke, ctm);
    else
        area = fz_bound_path(doc->ctx, path, NULL, ctm);

    if (navigate_uri_att)
        xps_add_link(doc, area, base_uri, navigate_uri_att);

    xps_begin_opacity(doc, ctm, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);

    if (fill_att)
    {
        xps_parse_color(doc, base_uri, fill_att, &colorspace, samples);
        if (fill_opacity_att)
            samples[0] = fz_atof(fill_opacity_att);
        xps_set_color(doc, colorspace, samples);

        fz_fill_path(doc->dev, path, fill_rule == 0, ctm,
                     doc->colorspace, doc->color, doc->alpha);
    }

    if (fill_tag)
    {
        area = fz_bound_path(doc->ctx, path, NULL, ctm);

        fz_clip_path(doc->dev, path, NULL, fill_rule == 0, ctm);
        xps_parse_brush(doc, ctm, area, fill_uri, dict, fill_tag);
        fz_pop_clip(doc->dev);
    }

    if (stroke_att)
    {
        xps_parse_color(doc, base_uri, stroke_att, &colorspace, samples);
        if (stroke_opacity_att)
            samples[0] = fz_atof(stroke_opacity_att);
        xps_set_color(doc, colorspace, samples);

        fz_stroke_path(doc->dev, path, &stroke, ctm,
                       doc->colorspace, doc->color, doc->alpha);
    }

    if (stroke_tag)
    {
        fz_clip_stroke_path(doc->dev, path, NULL, &stroke, ctm);
        xps_parse_brush(doc, ctm, area, stroke_uri, dict, stroke_tag);
        fz_pop_clip(doc->dev);
    }

    xps_end_opacity(doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);

    fz_free_path(doc->ctx, path);
    path = NULL;

    if (clip_att || clip_tag)
        fz_pop_clip(doc->dev);
}