Пример #1
0
/* Helper functions */
static void
pdf_dev_stroke_state(fz_context *ctx, pdf_device *pdev, const fz_stroke_state *stroke_state)
{
	gstate *gs = CURRENT_GSTATE(pdev);

	if (stroke_state == gs->stroke_state)
		return;
	if (gs->stroke_state && !memcmp(stroke_state, gs->stroke_state, sizeof(*stroke_state)))
		return;
	if (!gs->stroke_state || gs->stroke_state->linewidth != stroke_state->linewidth)
	{
		fz_append_printf(ctx, gs->buf, "%g w\n", stroke_state->linewidth);
	}
	if (!gs->stroke_state || gs->stroke_state->start_cap != stroke_state->start_cap)
	{
		int cap = stroke_state->start_cap;
		/* FIXME: Triangle caps aren't supported in pdf */
		if (cap == FZ_LINECAP_TRIANGLE)
			cap = FZ_LINECAP_BUTT;
		fz_append_printf(ctx, gs->buf, "%d J\n", cap);
	}
	if (!gs->stroke_state || gs->stroke_state->linejoin != stroke_state->linejoin)
	{
		int join = stroke_state->linejoin;
		if (join == FZ_LINEJOIN_MITER_XPS)
			join = FZ_LINEJOIN_MITER;
		fz_append_printf(ctx, gs->buf, "%d j\n", join);
	}
	if (!gs->stroke_state || gs->stroke_state->miterlimit != stroke_state->miterlimit)
	{
		fz_append_printf(ctx, gs->buf, "%g M\n", stroke_state->miterlimit);
	}
	if (gs->stroke_state == NULL && stroke_state->dash_len == 0)
	{}
	else if (!gs->stroke_state || gs->stroke_state->dash_phase != stroke_state->dash_phase || gs->stroke_state->dash_len != stroke_state->dash_len ||
		memcmp(gs->stroke_state->dash_list, stroke_state->dash_list, sizeof(float)*stroke_state->dash_len))
	{
		int i;
		if (stroke_state->dash_len == 0)
			fz_append_byte(ctx, gs->buf, '[');
		for (i = 0; i < stroke_state->dash_len; i++)
		{
			if (i > 0)
				fz_append_byte(ctx, gs->buf, ' ');
			fz_append_printf(ctx, gs->buf, "%g", stroke_state->dash_list[i]);
		}
		fz_append_printf(ctx, gs->buf, "]%g d\n", stroke_state->dash_phase);
	}
	fz_drop_stroke_state(ctx, gs->stroke_state);
	gs->stroke_state = fz_keep_stroke_state(ctx, stroke_state);
}
Пример #2
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);
}
Пример #3
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:
		break;
	}
	if (node->stroke)
		fz_drop_stroke_state(ctx, node->stroke);
	if (node->colorspace)
		fz_drop_colorspace(ctx, node->colorspace);
	fz_free(ctx, 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);
}
Пример #5
0
void
xps_parse_path(fz_context *ctx, xps_document *doc, fz_matrix ctm, char *base_uri, xps_resource *dict, fz_xml *root)
{
	fz_device *dev = doc->dev;

	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;

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

	/*
	 * 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");

	for (node = fz_xml_down(root); node; node = fz_xml_next(node))
	{
		if (fz_xml_is_tag(node, "Path.RenderTransform"))
			transform_tag = fz_xml_down(node);
		if (fz_xml_is_tag(node, "Path.OpacityMask"))
			opacity_mask_tag = fz_xml_down(node);
		if (fz_xml_is_tag(node, "Path.Clip"))
			clip_tag = fz_xml_down(node);
		if (fz_xml_is_tag(node, "Path.Fill"))
			fill_tag = fz_xml_down(node);
		if (fz_xml_is_tag(node, "Path.Stroke"))
			stroke_tag = fz_xml_down(node);
		if (fz_xml_is_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(ctx, doc, dict, &data_att, &data_tag, NULL);
	xps_resolve_resource_reference(ctx, doc, dict, &clip_att, &clip_tag, NULL);
	xps_resolve_resource_reference(ctx, doc, dict, &transform_att, &transform_tag, NULL);
	xps_resolve_resource_reference(ctx, doc, dict, &fill_att, &fill_tag, &fill_uri);
	xps_resolve_resource_reference(ctx, doc, dict, &stroke_att, &stroke_tag, &stroke_uri);
	xps_resolve_resource_reference(ctx, 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 (fz_xml_is_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 (fz_xml_is_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_dash_len(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++;
			}
			if (dash_len > 0)
			{
				/* fz_stroke_path doesn't draw non-empty paths with phase length zero */
				float phase_len = 0;
				int i;
				for (i = 0; i < dash_len; i++)
					phase_len += stroke->dash_list[i];
				if (phase_len == 0)
					dash_len = 0;
			}
			stroke->dash_len = dash_len;
		}
	}

	ctm = xps_parse_transform(ctx, doc, transform_att, transform_tag, ctm);

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

	fz_try(ctx)
	{
		fill_rule = 0;
		if (data_att)
			path = xps_parse_abbreviated_geometry(ctx, doc, data_att, &fill_rule);
		else if (data_tag)
		{
			path = xps_parse_path_geometry(ctx, doc, dict, data_tag, 0, &fill_rule);
			// /home/sebras/src/jxr/fts_06xx.xps
			if (stroke_att || stroke_tag)
				stroke_path = xps_parse_path_geometry(ctx, doc, dict, data_tag, 1, &fill_rule);
		}
		if (!stroke_path)
			stroke_path = path;

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

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

		if (fill_att)
		{
			xps_parse_color(ctx, doc, base_uri, fill_att, &colorspace, samples);
			if (fill_opacity_att)
				samples[0] *= fz_atof(fill_opacity_att);
			xps_set_color(ctx, doc, colorspace, samples);
			fz_fill_path(ctx, dev, path, fill_rule == 0, ctm,
				doc->colorspace, doc->color, doc->alpha, NULL);
		}

		if (fill_tag)
		{
			fz_clip_path(ctx, dev, path, fill_rule == 0, ctm, area);
			xps_parse_brush(ctx, doc, ctm, area, fill_uri, dict, fill_tag);
			fz_pop_clip(ctx, dev);
		}

		if (stroke_att)
		{
			xps_parse_color(ctx, doc, base_uri, stroke_att, &colorspace, samples);
			if (stroke_opacity_att)
				samples[0] *= fz_atof(stroke_opacity_att);
			xps_set_color(ctx, doc, colorspace, samples);
			fz_stroke_path(ctx, dev, stroke_path, stroke, ctm,
				doc->colorspace, doc->color, doc->alpha, NULL);
		}

		if (stroke_tag)
		{
			fz_clip_stroke_path(ctx, dev, stroke_path, stroke, ctm, area);
			xps_parse_brush(ctx, doc, ctm, area, stroke_uri, dict, stroke_tag);
			fz_pop_clip(ctx, dev);
		}

		xps_end_opacity(ctx, doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
	}
	fz_always(ctx)
	{
		if (stroke_path != path)
			fz_drop_path(ctx, stroke_path);
		fz_drop_path(ctx, path);
		fz_drop_stroke_state(ctx, stroke);
	}
	fz_catch(ctx)
		fz_rethrow(ctx);

	if (clip_att || clip_tag)
		fz_pop_clip(ctx, dev);
}