Example #1
0
void
pdf_add_annot_quad_point(fz_context *ctx, pdf_annot *annot, fz_rect bbox)
{
	pdf_document *doc = annot->page->doc;
	fz_matrix page_ctm, inv_page_ctm;
	pdf_obj *quad_points;

	check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	quad_points = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
	if (!pdf_is_array(ctx, quad_points))
	{
		quad_points = pdf_new_array(ctx, doc, 8);
		pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(QuadPoints), quad_points);
	}

	/* Contrary to the specification, the points within a QuadPoint are NOT ordered
	 * in a counterclockwise fashion. Experiments with Adobe's implementation
	 * indicates a cross-wise ordering is intended: ul, ur, ll, lr.
	 */
	fz_transform_rect(&bbox, &inv_page_ctm);
	pdf_array_push_real(ctx, quad_points, bbox.x0); /* ul */
	pdf_array_push_real(ctx, quad_points, bbox.y1);
	pdf_array_push_real(ctx, quad_points, bbox.x1); /* ur */
	pdf_array_push_real(ctx, quad_points, bbox.y1);
	pdf_array_push_real(ctx, quad_points, bbox.x0); /* ll */
	pdf_array_push_real(ctx, quad_points, bbox.y0);
	pdf_array_push_real(ctx, quad_points, bbox.x1); /* lr */
	pdf_array_push_real(ctx, quad_points, bbox.y0);

	pdf_dirty_annot(ctx, annot);
}
Example #2
0
void
pdf_set_annot_ink_list(fz_context *ctx, pdf_annot *annot, int n, const int *count, const float *v)
{
	pdf_document *doc = annot->page->doc;
	fz_matrix page_ctm, inv_page_ctm;
	pdf_obj *ink_list, *stroke;
	fz_point point;
	int i, k;

	if (pdf_annot_type(ctx, annot) != PDF_ANNOT_INK)
		fz_throw(ctx, FZ_ERROR_GENERIC, "cannot set InkList on non-ink annotations");

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	// TODO: update Rect (in update appearance perhaps?)

	ink_list = pdf_new_array(ctx, doc, n);
	for (i = 0; i < n; ++i)
	{
		stroke = pdf_new_array(ctx, doc, count[i]);
		for (k = 0; k < count[i]; ++k)
		{
			point.x = *v++;
			point.y = *v++;
			fz_transform_point(&point, &inv_page_ctm);
			pdf_array_push_drop(ctx, stroke, pdf_new_real(ctx, doc, point.x));
			pdf_array_push_drop(ctx, stroke, pdf_new_real(ctx, doc, point.y));
		}
		pdf_array_push_drop(ctx, ink_list, stroke);
	}
	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_InkList, ink_list);
	annot->changed = 1;
}
Example #3
0
void render_page(void)
{
    fz_annot *annot;
    fz_pixmap *pix;

    fz_scale(&page_ctm, currentzoom / 72, currentzoom / 72);
    fz_pre_rotate(&page_ctm, -currentrotate);
    fz_invert_matrix(&page_inv_ctm, &page_ctm);

    fz_drop_page(ctx, page);

    page = fz_load_page(ctx, doc, currentpage);

    fz_drop_link(ctx, links);
    links = NULL;
    links = fz_load_links(ctx, page);

    pix = fz_new_pixmap_from_page_contents(ctx, page, &page_ctm, fz_device_rgb(ctx), 0);
    texture_from_pixmap(&page_tex, pix);
    fz_drop_pixmap(ctx, pix);

    annot_count = 0;
    for (annot = fz_first_annot(ctx, page); annot; annot = fz_next_annot(ctx, annot))
    {
        pix = fz_new_pixmap_from_annot(ctx, annot, &page_ctm, fz_device_rgb(ctx), 1);
        texture_from_pixmap(&annot_tex[annot_count++], pix);
        fz_drop_pixmap(ctx, pix);
        if (annot_count >= nelem(annot_tex))
        {
            fz_warn(ctx, "too many annotations to display!");
            break;
        }
    }

}
void
pdf_set_annot_ink_list(fz_context *ctx, pdf_annot *annot, int n, const int *count, const float *v)
{
	pdf_document *doc = annot->page->doc;
	fz_matrix page_ctm, inv_page_ctm;
	pdf_obj *ink_list, *stroke;
	fz_point point;
	int i, k;

	check_allowed_subtypes(ctx, annot, PDF_NAME_InkList, ink_list_subtypes);

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	// TODO: update Rect (in update appearance perhaps?)

	ink_list = pdf_new_array(ctx, doc, n);
	for (i = 0; i < n; ++i)
	{
		stroke = pdf_new_array(ctx, doc, count[i]);
		for (k = 0; k < count[i]; ++k)
		{
			point.x = *v++;
			point.y = *v++;
			fz_transform_point(&point, &inv_page_ctm);
			pdf_array_push_drop(ctx, stroke, pdf_new_real(ctx, doc, point.x));
			pdf_array_push_drop(ctx, stroke, pdf_new_real(ctx, doc, point.y));
		}
		pdf_array_push_drop(ctx, ink_list, stroke);
	}
	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_InkList, ink_list);
	annot->changed = 1;
}
void
pdf_set_annot_vertices(fz_context *ctx, pdf_annot *annot, int n, const float *v)
{
	pdf_document *doc = annot->page->doc;
	fz_matrix page_ctm, inv_page_ctm;
	pdf_obj *vertices;
	fz_point point;
	int i;

	check_allowed_subtypes(ctx, annot, PDF_NAME_Vertices, vertices_subtypes);
	if (n <= 0 || !v)
		fz_throw(ctx, FZ_ERROR_GENERIC, "invalid number of vertices");

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	vertices = pdf_new_array(ctx, doc, n * 2);
	for (i = 0; i < n; ++i)
	{
		point.x = v[i * 2];
		point.y = v[i * 2 + 1];
		fz_transform_point(&point, &inv_page_ctm);
		pdf_array_push_drop(ctx, vertices, pdf_new_real(ctx, doc, point.x));
		pdf_array_push_drop(ctx, vertices, pdf_new_real(ctx, doc, point.y));
	}
	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_Vertices, vertices);
	annot->changed = 1;
}
void
pdf_set_annot_quad_points(fz_context *ctx, pdf_annot *annot, int n, const float *v)
{
	pdf_document *doc = annot->page->doc;
	fz_matrix page_ctm, inv_page_ctm;
	pdf_obj *quad_points;
	fz_point point;
	int i, k;

	check_allowed_subtypes(ctx, annot, PDF_NAME_QuadPoints, quad_point_subtypes);
	if (n <= 0 || !v)
		fz_throw(ctx, FZ_ERROR_GENERIC, "invalid number of quadrilaterals");

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	quad_points = pdf_new_array(ctx, doc, n * 8);
	for (i = 0; i < n; ++i)
	{
		for (k = 0; k < 4; ++k)
		{
			point.x = v[i * 8 + k * 2 + 0];
			point.y = v[i * 8 + k * 2 + 1];
			fz_transform_point(&point, &inv_page_ctm);
			pdf_array_push_drop(ctx, quad_points, pdf_new_real(ctx, doc, point.x));
			pdf_array_push_drop(ctx, quad_points, pdf_new_real(ctx, doc, point.y));
		}
	}
	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_QuadPoints, quad_points);
	annot->changed = 1;
}
Example #7
0
void
pdf_set_annot_quad_points(fz_context *ctx, pdf_annot *annot, int n, const float *v)
{
	pdf_document *doc = annot->page->doc;
	fz_matrix page_ctm, inv_page_ctm;
	pdf_obj *quad_points;
	fz_point point;
	int i, k;

	// TODO: check annot type

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	quad_points = pdf_new_array(ctx, doc, n * 8);
	for (i = 0; i < n; ++i)
	{
		for (k = 0; k < 4; ++k)
		{
			point.x = v[i * 8 + k * 2 + 0];
			point.y = v[i * 8 + k * 2 + 1];
			fz_transform_point(&point, &inv_page_ctm);
			pdf_array_push_drop(ctx, quad_points, pdf_new_real(ctx, doc, point.x));
			pdf_array_push_drop(ctx, quad_points, pdf_new_real(ctx, doc, point.y));
		}
	}
	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_QuadPoints, quad_points);
	annot->changed = 1;
}
Example #8
0
static void
svg_dev_text(svg_device *sdev, const fz_matrix *ctm, fz_text *text)
{
	fz_output *out = sdev->out;
	int i;
	fz_matrix inverse;
	fz_matrix local_trm;
	float size;

	/* Rely on the fact that trm.{e,f} == 0 */
	size = fz_matrix_expansion(&text->trm);
	local_trm.a = text->trm.a / size;
	local_trm.b = text->trm.b / size;
	local_trm.c = -text->trm.c / size;
	local_trm.d = -text->trm.d / size;
	local_trm.e = 0;
	local_trm.f = 0;
	fz_invert_matrix(&inverse, &local_trm);
	fz_concat(&local_trm, &local_trm, ctm);

	fz_printf(out, " transform=\"matrix(%g,%g,%g,%g,%g,%g)\"",
		local_trm.a, local_trm.b, local_trm.c, local_trm.d, local_trm.e, local_trm.f);
	fz_printf(out, " font-size=\"%g\"", size);
	fz_printf(out, " font-family=\"%s\"", text->font->name);

	fz_printf(out, " x=");
	for (i=0; i < text->len; i++)
	{
		fz_text_item *it = &text->items[i];
		fz_point p;
		p.x = it->x;
		p.y = it->y;
		fz_transform_point(&p, &inverse);
		fz_printf(out, "%c%g", i == 0 ? '\"' : ' ', p.x);
	}
	fz_printf(out, "\" y=");
	for (i=0; i < text->len; i++)
	{
		fz_text_item *it = &text->items[i];
		fz_point p;
		p.x = it->x;
		p.y = it->y;
		fz_transform_point(&p, &inverse);
		fz_printf(out, "%c%g", i == 0 ? '\"' : ' ', p.y);
	}
	fz_printf(out, "\">\n");
	for (i=0; i < text->len; i++)
	{
		fz_text_item *it = &text->items[i];
		int c = it->ucs;
		if (c >= 32 && c <= 127 && c != '<' && c != '&')
			fz_printf(out, "%c", c);
		else
			fz_printf(out, "&#x%04x;", c);
	}
	fz_printf(out, "\n</text>\n");
}
Example #9
0
static void
xps_draw_linear_gradient(fz_context *ctx, xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
	struct stop *stops, int count,
	fz_xml *root, int spread)
{
	float x0, y0, x1, y1;
	int i, mi, ma;
	float dx, dy, x, y, k;
	fz_point p1, p2;
	fz_matrix inv;
	fz_rect local_area = *area;

	char *start_point_att = fz_xml_att(root, "StartPoint");
	char *end_point_att = fz_xml_att(root, "EndPoint");

	x0 = y0 = 0;
	x1 = y1 = 1;

	if (start_point_att)
		xps_parse_point(ctx, doc, start_point_att, &x0, &y0);
	if (end_point_att)
		xps_parse_point(ctx, doc, end_point_att, &x1, &y1);

	p1.x = x0; p1.y = y0; p2.x = x1; p2.y = y1;
	fz_transform_rect(&local_area, fz_invert_matrix(&inv, ctm));
	x = p2.x - p1.x; y = p2.y - p1.y;
	k = ((local_area.x0 - p1.x) * x + (local_area.y0 - p1.y) * y) / (x * x + y * y);
	mi = floorf(k); ma = ceilf(k);
	k = ((local_area.x1 - p1.x) * x + (local_area.y0 - p1.y) * y) / (x * x + y * y);
	mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
	k = ((local_area.x0 - p1.x) * x + (local_area.y1 - p1.y) * y) / (x * x + y * y);
	mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
	k = ((local_area.x1 - p1.x) * x + (local_area.y1 - p1.y) * y) / (x * x + y * y);
	mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
	dx = x1 - x0; dy = y1 - y0;

	if (spread == SPREAD_REPEAT)
	{
		for (i = mi; i < ma; i++)
			xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 0, x0 + i * dx, y0 + i * dy, x1 + i * dx, y1 + i * dy);
	}
	else if (spread == SPREAD_REFLECT)
	{
		if ((mi % 2) != 0)
			mi--;
		for (i = mi; i < ma; i += 2)
		{
			xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 0, x0 + i * dx, y0 + i * dy, x1 + i * dx, y1 + i * dy);
			xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 0, x0 + (i + 2) * dx, y0 + (i + 2) * dy, x1 + i * dx, y1 + i * dy);
		}
	}
	else
	{
		xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 1, x0, y0, x1, y1);
	}
}
Example #10
0
static void
pdf_dev_ctm(fz_context *ctx, pdf_device *pdev, fz_matrix ctm)
{
	fz_matrix inverse;
	gstate *gs = CURRENT_GSTATE(pdev);

	if (memcmp(&gs->ctm, &ctm, sizeof(ctm)) == 0)
		return;
	inverse = fz_invert_matrix(gs->ctm);
	inverse = fz_concat(ctm, inverse);
	gs->ctm = ctm;
	fz_append_printf(ctx, gs->buf, "%M cm\n", &inverse);
}
Example #11
0
void
pdf_set_annot_rect(fz_context *ctx, pdf_annot *annot, const fz_rect *rect)
{
	fz_rect trect = *rect;
	fz_matrix page_ctm, inv_page_ctm;

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);
	fz_transform_rect(&trect, &inv_page_ctm);

	pdf_dict_put_rect(ctx, annot->obj, PDF_NAME(Rect), &trect);
	pdf_dirty_annot(ctx, annot);
}
Example #12
0
static void
pdf_dev_ctm(fz_context *ctx, pdf_device *pdev, const fz_matrix *ctm)
{
	fz_matrix inverse;
	gstate *gs = CURRENT_GSTATE(pdev);

	if (memcmp(&gs->ctm, ctm, sizeof(*ctm)) == 0)
		return;
	fz_invert_matrix(&inverse, &gs->ctm);
	fz_concat(&inverse, ctm, &inverse);
	gs->ctm = *ctm;
	fz_buffer_printf(ctx, gs->buf, "%M cm\n", &inverse);
}
Example #13
0
static int
svg_dev_begin_tile(fz_device *dev, const fz_rect *area, const fz_rect *view, float xstep, float ystep, const fz_matrix *ctm, int id)
{
	svg_device *sdev = (svg_device *)dev->user;
	fz_output *out = sdev->out;
	fz_context *ctx = dev->ctx;
	fz_matrix inverse;
	int num;
	tile *t;

	if (sdev->num_tiles == sdev->max_tiles)
	{
		int n = (sdev->num_tiles == 0 ? 4 : sdev->num_tiles * 2);

		sdev->tiles = fz_resize_array(ctx, sdev->tiles, n, sizeof(tile));
		sdev->max_tiles = n;
	}
	num = sdev->num_tiles++;
	t = &sdev->tiles[num];
	t->area = *area;
	t->view = *view;
	t->ctm = *ctm;
	t->pattern = sdev->id++;
	t->step.x = xstep;
	t->step.y = ystep;

	/* view = area of our reference tile in pattern space.
	 * area = area to tile into in pattern space.
	 * xstep/ystep = pattern repeat step in pattern space.
	 * All of these need to be transformed by ctm to get to device space.
	 * SVG only allows us to specify pattern tiles as axis aligned
	 * rectangles, so we send these through as is, and ensure that the
	 * correct matrix is used on the fill.
	 */

	/* In svg, the reference tile is taken from (x,y) to (x+width,y+height)
	 * and is repeated at (x+n*width,y+m*height) for all integer n and m.
	 * This means that width and height correspond to xstep and ystep. */
	fz_printf(out, "<pattern id=\"pa%d\" patternUnits=\"userSpaceOnUse\" patternContentUnits=\"userSpaceOnUse\"",
		t->pattern);
	fz_printf(out, " x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\">\n",
		view->x0, view->y0, xstep, ystep);
	/* All the pattern contents will have their own ctm applied. Let's
	 * undo the current one to allow for this */
	fz_invert_matrix(&inverse, ctm);
	fz_printf(out, "<g");
	svg_dev_ctm(sdev, &inverse);
	fz_printf(out, ">\n");

	return 0;
}
Example #14
0
JNIEXPORT int JNICALL
Java_com_artifex_mupdf_MuPDFCore_passClickEventInternal(JNIEnv * env, jobject thiz, int pageNumber, float x, float y)
{
	fz_matrix ctm;
	fz_interactive *idoc = fz_interact(doc);
	float zoom;
	fz_point p;
	fz_ui_event event;
	int changed = 0;
	page_cache *pc;

	if (idoc == NULL)
		return 0;

	Java_com_artifex_mupdf_MuPDFCore_gotoPageInternal(env, thiz, pageNumber);
	pc = &pages[current];
	if (pc->number != pageNumber || pc->page == NULL)
		return 0;

	p.x = x;
	p.y = y;

	/* Ultimately we should probably return a pointer to a java structure
	 * with the link details in, but for now, page number will suffice.
	 */
	zoom = resolution / 72;
	ctm = fz_scale(zoom, zoom);
	ctm = fz_invert_matrix(ctm);

	p = fz_transform_point(ctm, p);

	fz_try(ctx)
	{
		event.etype = FZ_EVENT_TYPE_POINTER;
		event.event.pointer.pt = p;
		event.event.pointer.ptype = FZ_POINTER_DOWN;
		changed = fz_pass_event(idoc, pc->page, &event);
		event.event.pointer.ptype = FZ_POINTER_UP;
		changed |= fz_pass_event(idoc, pc->page, &event);
		if (changed) {
			dump_annotation_display_lists();
		}
	}
	fz_catch(ctx)
	{
		LOGE("passClickEvent: %s", ctx->error->message);
	}

	return changed;
}
void
pdf_set_annot_rect(fz_context *ctx, pdf_annot *annot, const fz_rect *rect)
{
	pdf_document *doc = annot->page->doc;
	fz_rect trect = *rect;
	fz_matrix page_ctm, inv_page_ctm;

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);
	fz_transform_rect(&trect, &inv_page_ctm);

	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_Rect, pdf_new_rect(ctx, doc, &trect));
	annot->changed = 1;
}
Example #16
0
void pdf_set_annot_vertex(fz_context *ctx, pdf_annot *annot, int i, fz_point p)
{
	fz_matrix page_ctm, inv_page_ctm;
	pdf_obj *vertices;

	check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	fz_transform_point(&p, &inv_page_ctm);

	vertices = pdf_dict_get(ctx, annot->obj, PDF_NAME(Vertices));
	pdf_array_put_drop(ctx, vertices, i * 2 + 0, pdf_new_real(ctx, p.x));
	pdf_array_put_drop(ctx, vertices, i * 2 + 1, pdf_new_real(ctx, p.y));
}
Example #17
0
JNIEXPORT int JNICALL
Java_com_artifex_mupdf_MuPDFCore_getPageLink(JNIEnv * env, jobject thiz, int pageNumber, float x, float y)
{
	fz_matrix ctm;
	float zoom;
	fz_link *link;
	fz_point p;
	page_cache *pc;

	Java_com_artifex_mupdf_MuPDFCore_gotoPageInternal(env, thiz, pageNumber);
	pc = &pages[current];
	if (pc->number != pageNumber || pc->page == NULL)
		return -1;

	p.x = x;
	p.y = y;

	/* Ultimately we should probably return a pointer to a java structure
	 * with the link details in, but for now, page number will suffice.
	 */
	zoom = resolution / 72;
	ctm = fz_scale(zoom, zoom);
	ctm = fz_invert_matrix(ctm);

	p = fz_transform_point(ctm, p);

	for (link = fz_load_links(doc, pc->page); link; link = link->next)
	{
		if (p.x >= link->rect.x0 && p.x <= link->rect.x1)
			if (p.y >= link->rect.y0 && p.y <= link->rect.y1)
				break;
	}

	if (link == NULL)
		return -1;

	if (link->dest.kind == FZ_LINK_URI)
	{
		//gotouri(link->dest.ld.uri.uri);
		return -1;
	}
	else if (link->dest.kind == FZ_LINK_GOTO)
		return link->dest.ld.gotor.page;
	return -1;
}
Example #18
0
void
pdf_add_annot_ink_list(fz_context *ctx, pdf_annot *annot, int n, fz_point p[])
{
	pdf_document *doc = annot->page->doc;
	fz_matrix page_ctm, inv_page_ctm;
	pdf_obj *ink_list, *stroke;
	int i;

	check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	ink_list = pdf_dict_get(ctx, annot->obj, PDF_NAME(InkList));
	if (!pdf_is_array(ctx, ink_list))
	{
		ink_list = pdf_new_array(ctx, doc, 10);
		pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(InkList), ink_list);
	}

	stroke = pdf_new_array(ctx, doc, n * 2);
	fz_try(ctx)
	{
		for (i = 0; i < n; ++i)
		{
			fz_point tp = p[i];
			fz_transform_point(&tp, &inv_page_ctm);
			pdf_array_push_real(ctx, stroke, tp.x);
			pdf_array_push_real(ctx, stroke, tp.y);
		}
	}
	fz_catch(ctx)
	{
		pdf_drop_obj(ctx, stroke);
		fz_rethrow(ctx);
	}

	pdf_array_push_drop(ctx, ink_list, stroke);

	pdf_dirty_annot(ctx, annot);
}
Example #19
0
void pdf_set_text_annot_position(fz_context *ctx, pdf_document *doc, pdf_annot *annot, fz_point pt)
{
	fz_matrix ctm;
	fz_rect rect;
	int flags;

	fz_invert_matrix(&ctm, &annot->page->ctm);
	rect.x0 = pt.x;
	rect.x1 = pt.x + TEXT_ANNOT_SIZE;
	rect.y0 = pt.y;
	rect.y1 = pt.y + TEXT_ANNOT_SIZE;
	fz_transform_rect(&rect, &ctm);

	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_Rect, pdf_new_rect(ctx, doc, &rect));

	flags = pdf_to_int(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME_F));
	flags |= (F_NoZoom|F_NoRotate);
	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_F, pdf_new_int(ctx, doc, flags));

	update_rect(ctx, annot);
}
Example #20
0
void
pdf_set_text_annot_position(fz_context *ctx, pdf_annot *annot, fz_point pt)
{
	fz_matrix page_ctm, inv_page_ctm;
	fz_rect rect;
	int flags;

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	rect.x0 = pt.x;
	rect.x1 = pt.x + TEXT_ANNOT_SIZE;
	rect.y0 = pt.y;
	rect.y1 = pt.y + TEXT_ANNOT_SIZE;
	fz_transform_rect(&rect, &inv_page_ctm);

	pdf_dict_put_rect(ctx, annot->obj, PDF_NAME(Rect), &rect);

	flags = pdf_dict_get_int(ctx, annot->obj, PDF_NAME(F));
	flags |= (PDF_ANNOT_IS_NO_ZOOM|PDF_ANNOT_IS_NO_ROTATE);
	pdf_dict_put_int(ctx, annot->obj, PDF_NAME(F), flags);
}
Example #21
0
void pdf_set_text_annot_position(fz_context *ctx, pdf_annot *annot, fz_point pt)
{
	pdf_document *doc = annot->page->doc;
	fz_matrix page_ctm, inv_page_ctm;
	fz_rect rect;
	int flags;

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	rect.x0 = pt.x;
	rect.x1 = pt.x + TEXT_ANNOT_SIZE;
	rect.y0 = pt.y;
	rect.y1 = pt.y + TEXT_ANNOT_SIZE;
	fz_transform_rect(&rect, &inv_page_ctm);

	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_Rect, pdf_new_rect(ctx, doc, &rect));

	flags = pdf_to_int(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME_F));
	flags |= (PDF_ANNOT_IS_NO_ZOOM|PDF_ANNOT_IS_NO_ROTATE);
	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_F, pdf_new_int(ctx, doc, flags));
}
Example #22
0
void
pdf_set_annot_line(fz_context *ctx, pdf_annot *annot, fz_point a, fz_point b)
{
	fz_matrix page_ctm, inv_page_ctm;
	pdf_obj *line;

	check_allowed_subtypes(ctx, annot, PDF_NAME(L), line_subtypes);

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	fz_transform_point(&a, &inv_page_ctm);
	fz_transform_point(&b, &inv_page_ctm);

	line = pdf_new_array(ctx, annot->page->doc, 4);
	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(L), line);
	pdf_array_push_real(ctx, line, a.x);
	pdf_array_push_real(ctx, line, a.y);
	pdf_array_push_real(ctx, line, b.x);
	pdf_array_push_real(ctx, line, b.y);

	pdf_dirty_annot(ctx, annot);
}
Example #23
0
void
pdf_set_markup_annot_quadpoints(fz_context *ctx, pdf_document *doc, pdf_annot *annot, fz_point *qp, int n)
{
	fz_matrix ctm;
	pdf_obj *arr = pdf_new_array(ctx, doc, n*2);
	int i;

	fz_invert_matrix(&ctm, &annot->page->ctm);

	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_QuadPoints, arr);

	for (i = 0; i < n; i++)
	{
		fz_point pt = qp[i];
		pdf_obj *r;

		fz_transform_point(&pt, &ctm);
		r = pdf_new_real(ctx, doc, pt.x);
		pdf_array_push_drop(ctx, arr, r);
		r = pdf_new_real(ctx, doc, pt.y);
		pdf_array_push_drop(ctx, arr, r);
	}
}
Example #24
0
void pdf_add_annot_vertex(fz_context *ctx, pdf_annot *annot, fz_point p)
{
	pdf_document *doc = annot->page->doc;
	fz_matrix page_ctm, inv_page_ctm;
	pdf_obj *vertices;

	check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	vertices = pdf_dict_get(ctx, annot->obj, PDF_NAME(Vertices));
	if (!pdf_is_array(ctx, vertices))
	{
		vertices = pdf_new_array(ctx, doc, 32);
		pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(Vertices), vertices);
	}

	fz_transform_point(&p, &inv_page_ctm);
	pdf_array_push_real(ctx, vertices, p.x);
	pdf_array_push_real(ctx, vertices, p.y);

	pdf_dirty_annot(ctx, annot);
}
Example #25
0
void
pdf_set_ink_annot_list(fz_context *ctx, pdf_document *doc, pdf_annot *annot, fz_point *pts, int *counts, int ncount, float color[3], float thickness)
{
	fz_matrix ctm;
	pdf_obj *list = pdf_new_array(ctx, doc, ncount);
	pdf_obj *bs, *col;
	fz_rect rect;
	int i, k = 0;

	fz_invert_matrix(&ctm, &annot->page->ctm);

	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_InkList, list);

	for (i = 0; i < ncount; i++)
	{
		int j;
		pdf_obj *arc = pdf_new_array(ctx, doc, counts[i]);

		pdf_array_push_drop(ctx, list, arc);

		for (j = 0; j < counts[i]; j++)
		{
			fz_point pt = pts[k];

			fz_transform_point(&pt, &ctm);

			if (i == 0 && j == 0)
			{
				rect.x0 = rect.x1 = pt.x;
				rect.y0 = rect.y1 = pt.y;
			}
			else
			{
				fz_include_point_in_rect(&rect, &pt);
			}

			pdf_array_push_drop(ctx, arc, pdf_new_real(ctx, doc, pt.x));
			pdf_array_push_drop(ctx, arc, pdf_new_real(ctx, doc, pt.y));
			k++;
		}
	}

	/*
		Expand the rectangle by thickness all around. We cannot use
		fz_expand_rect because the rectangle might be empty in the
		single point case
	*/
	if (k > 0)
	{
		rect.x0 -= thickness;
		rect.y0 -= thickness;
		rect.x1 += thickness;
		rect.y1 += thickness;
	}

	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_Rect, pdf_new_rect(ctx, doc, &rect));
	update_rect(ctx, annot);

	bs = pdf_new_dict(ctx, doc, 1);
	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_BS, bs);
	pdf_dict_put_drop(ctx, bs, PDF_NAME_W, pdf_new_real(ctx, doc, thickness));

	col = pdf_new_array(ctx, doc, 3);
	pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_C, col);
	for (i = 0; i < 3; i++)
		pdf_array_push_drop(ctx, col, pdf_new_real(ctx, doc, color[i]));
}
Example #26
0
static void
xps_draw_radial_gradient(fz_context *ctx, xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
	struct stop *stops, int count,
	fz_xml *root, int spread)
{
	float x0, y0, r0;
	float x1, y1, r1;
	float xrad = 1;
	float yrad = 1;
	float invscale;
	int i, ma = 1;
	fz_matrix local_ctm = *ctm;
	fz_matrix inv;
	fz_rect local_area = *area;

	char *center_att = fz_xml_att(root, "Center");
	char *origin_att = fz_xml_att(root, "GradientOrigin");
	char *radius_x_att = fz_xml_att(root, "RadiusX");
	char *radius_y_att = fz_xml_att(root, "RadiusY");

	x0 = y0 = 0.0;
	x1 = y1 = 1.0;
	xrad = 1.0;
	yrad = 1.0;

	if (origin_att)
		xps_parse_point(ctx, doc, origin_att, &x0, &y0);
	if (center_att)
		xps_parse_point(ctx, doc, center_att, &x1, &y1);
	if (radius_x_att)
		xrad = fz_atof(radius_x_att);
	if (radius_y_att)
		yrad = fz_atof(radius_y_att);

	xrad = fz_max(0.01f, xrad);
	yrad = fz_max(0.01f, yrad);

	/* scale the ctm to make ellipses */
	if (fz_abs(xrad) > FLT_EPSILON)
	{
		fz_pre_scale(&local_ctm, 1, yrad/xrad);
	}

	if (yrad != 0.0)
	{
		invscale = xrad / yrad;
		y0 = y0 * invscale;
		y1 = y1 * invscale;
	}

	r0 = 0;
	r1 = xrad;

	fz_transform_rect(&local_area, fz_invert_matrix(&inv, &local_ctm));
	ma = fz_maxi(ma, ceilf(hypotf(local_area.x0 - x0, local_area.y0 - y0) / xrad));
	ma = fz_maxi(ma, ceilf(hypotf(local_area.x1 - x0, local_area.y0 - y0) / xrad));
	ma = fz_maxi(ma, ceilf(hypotf(local_area.x0 - x0, local_area.y1 - y0) / xrad));
	ma = fz_maxi(ma, ceilf(hypotf(local_area.x1 - x0, local_area.y1 - y0) / xrad));

	if (spread == SPREAD_REPEAT)
	{
		for (i = ma - 1; i >= 0; i--)
			xps_draw_one_radial_gradient(ctx, doc, &local_ctm, stops, count, 0, x0, y0, r0 + i * xrad, x1, y1, r1 + i * xrad);
	}
	else if (spread == SPREAD_REFLECT)
	{
		if ((ma % 2) != 0)
			ma++;
		for (i = ma - 2; i >= 0; i -= 2)
		{
			xps_draw_one_radial_gradient(ctx, doc, &local_ctm, stops, count, 0, x0, y0, r0 + i * xrad, x1, y1, r1 + i * xrad);
			xps_draw_one_radial_gradient(ctx, doc, &local_ctm, stops, count, 0, x0, y0, r0 + (i + 2) * xrad, x1, y1, r1 + i * xrad);
		}
	}
	else
	{
		xps_draw_one_radial_gradient(ctx, doc, &local_ctm, stops, count, 1, x0, y0, r0, x1, y1, r1);
	}
}
Example #27
0
void
xps_parse_tiling_brush(fz_context *ctx, xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
	char *base_uri, xps_resource *dict, fz_xml *root,
	void (*func)(fz_context *ctx, xps_document*, const fz_matrix*, const fz_rect*, char*, xps_resource*, fz_xml*, void*), void *user)
{
	fz_device *dev = doc->dev;
	fz_xml *node;
	struct closure c;

	char *opacity_att;
	char *transform_att;
	char *viewbox_att;
	char *viewport_att;
	char *tile_mode_att;

	fz_xml *transform_tag = NULL;

	fz_matrix transform;
	fz_rect viewbox;
	fz_rect viewport;
	float xstep, ystep;
	float xscale, yscale;
	int tile_mode;

	opacity_att = fz_xml_att(root, "Opacity");
	transform_att = fz_xml_att(root, "Transform");
	viewbox_att = fz_xml_att(root, "Viewbox");
	viewport_att = fz_xml_att(root, "Viewport");
	tile_mode_att = fz_xml_att(root, "TileMode");

	c.base_uri = base_uri;
	c.dict = dict;
	c.root = root;
	c.user = user;
	c.func = func;

	for (node = fz_xml_down(root); node; node = fz_xml_next(node))
	{
		if (fz_xml_is_tag(node, "ImageBrush.Transform"))
			transform_tag = fz_xml_down(node);
		if (fz_xml_is_tag(node, "VisualBrush.Transform"))
			transform_tag = fz_xml_down(node);
	}

	xps_resolve_resource_reference(ctx, doc, dict, &transform_att, &transform_tag, NULL);

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

	viewbox = fz_unit_rect;
	if (viewbox_att)
		xps_parse_rectangle(ctx, doc, viewbox_att, &viewbox);

	viewport = fz_unit_rect;
	if (viewport_att)
		xps_parse_rectangle(ctx, doc, viewport_att, &viewport);

	if (fabsf(viewport.x1 - viewport.x0) < 0.01f || fabsf(viewport.y1 - viewport.y0) < 0.01f)
		fz_warn(ctx, "not drawing tile for viewport size %.4f x %.4f", viewport.x1 - viewport.x0, viewport.y1 - viewport.y0);
	else if (fabsf(viewbox.x1 - viewbox.x0) < 0.01f || fabsf(viewbox.y1 - viewbox.y0) < 0.01f)
		fz_warn(ctx, "not drawing tile for viewbox size %.4f x %.4f", viewbox.x1 - viewbox.x0, viewbox.y1 - viewbox.y0);

	/* some sanity checks on the viewport/viewbox size */
	if (fabsf(viewport.x1 - viewport.x0) < 0.01f) return;
	if (fabsf(viewport.y1 - viewport.y0) < 0.01f) return;
	if (fabsf(viewbox.x1 - viewbox.x0) < 0.01f) return;
	if (fabsf(viewbox.y1 - viewbox.y0) < 0.01f) return;

	xstep = viewbox.x1 - viewbox.x0;
	ystep = viewbox.y1 - viewbox.y0;

	xscale = (viewport.x1 - viewport.x0) / xstep;
	yscale = (viewport.y1 - viewport.y0) / ystep;

	tile_mode = TILE_NONE;
	if (tile_mode_att)
	{
		if (!strcmp(tile_mode_att, "None"))
			tile_mode = TILE_NONE;
		if (!strcmp(tile_mode_att, "Tile"))
			tile_mode = TILE_TILE;
		if (!strcmp(tile_mode_att, "FlipX"))
			tile_mode = TILE_FLIP_X;
		if (!strcmp(tile_mode_att, "FlipY"))
			tile_mode = TILE_FLIP_Y;
		if (!strcmp(tile_mode_att, "FlipXY"))
			tile_mode = TILE_FLIP_X_Y;
	}

	if (tile_mode == TILE_FLIP_X || tile_mode == TILE_FLIP_X_Y)
		xstep *= 2;
	if (tile_mode == TILE_FLIP_Y || tile_mode == TILE_FLIP_X_Y)
		ystep *= 2;

	xps_begin_opacity(ctx, doc, &transform, area, base_uri, dict, opacity_att, NULL);

	fz_pre_translate(&transform, viewport.x0, viewport.y0);
	fz_pre_scale(&transform, xscale, yscale);
	fz_pre_translate(&transform, -viewbox.x0, -viewbox.y0);

	if (tile_mode != TILE_NONE)
	{
		int x0, y0, x1, y1;
		fz_matrix invctm;
		fz_rect local_area = *area;
		fz_transform_rect(&local_area, fz_invert_matrix(&invctm, &transform));
		x0 = floorf(local_area.x0 / xstep);
		y0 = floorf(local_area.y0 / ystep);
		x1 = ceilf(local_area.x1 / xstep);
		y1 = ceilf(local_area.y1 / ystep);

#ifdef TILE
		if ((x1 - x0) * (y1 - y0) > 1)
#else
		if (0)
#endif
		{
			fz_rect bigview = viewbox;
			bigview.x1 = bigview.x0 + xstep;
			bigview.y1 = bigview.y0 + ystep;
			fz_begin_tile(ctx, dev, &local_area, &bigview, xstep, ystep, &transform);
			xps_paint_tiling_brush(ctx, doc, &transform, &viewbox, tile_mode, &c);
			fz_end_tile(ctx, dev);
		}
		else
		{
			int x, y;
			for (y = y0; y < y1; y++)
			{
				for (x = x0; x < x1; x++)
				{
					fz_matrix ttm = transform;
					fz_pre_translate(&ttm, xstep * x, ystep * y);
					xps_paint_tiling_brush(ctx, doc, &ttm, &viewbox, tile_mode, &c);
				}
			}
		}
	}
	else
	{
		xps_paint_tiling_brush(ctx, doc, &transform, &viewbox, tile_mode, &c);
	}

	xps_end_opacity(ctx, doc, base_uri, dict, opacity_att, NULL);
}
void
pdf_set_free_text_details(fz_context *ctx, pdf_annot *annot, fz_point *pos, char *text, char *font_name, float font_size, float color[3])
{
	pdf_document *doc = annot->page->doc;
	char nbuf[32];
	pdf_obj *dr;
	pdf_obj *form_fonts;
	pdf_obj *font = NULL;
	pdf_obj *ref;
	pdf_font_desc *font_desc = NULL;
	fz_matrix page_ctm, inv_page_ctm;
	pdf_da_info da_info;
	fz_buffer *fzbuf = NULL;
	fz_point page_pos;

	pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
	fz_invert_matrix(&inv_page_ctm, &page_ctm);

	dr = pdf_dict_get(ctx, annot->page->obj, PDF_NAME_Resources);
	if (!dr)
	{
		dr = pdf_new_dict(ctx, doc, 1);
		pdf_dict_put_drop(ctx, annot->page->obj, PDF_NAME_Resources, dr);
	}

	/* Ensure the resource dictionary includes a font dict */
	form_fonts = pdf_dict_get(ctx, dr, PDF_NAME_Font);
	if (!form_fonts)
	{
		form_fonts = pdf_new_dict(ctx, doc, 1);
		pdf_dict_put_drop(ctx, dr, PDF_NAME_Font, form_fonts);
		/* form_fonts is still valid if execution continues past the above call */
	}

	fz_var(fzbuf);
	fz_var(font);
	fz_try(ctx)
	{
		unsigned char *da_str;
		size_t da_len;
		fz_rect bounds;

		find_free_font_name(ctx, form_fonts, nbuf, sizeof(nbuf));

		font = pdf_new_dict(ctx, doc, 5);
		ref = pdf_add_object(ctx, doc, font);
		pdf_dict_puts_drop(ctx, form_fonts, nbuf, ref);

		pdf_dict_put_drop(ctx, font, PDF_NAME_Type, PDF_NAME_Font);
		pdf_dict_put_drop(ctx, font, PDF_NAME_Subtype, PDF_NAME_Type1);
		pdf_dict_put_drop(ctx, font, PDF_NAME_BaseFont, pdf_new_name(ctx, doc, font_name));
		pdf_dict_put_drop(ctx, font, PDF_NAME_Encoding, PDF_NAME_WinAnsiEncoding);

		memcpy(da_info.col, color, sizeof(float)*3);
		da_info.col_size = 3;
		da_info.font_name = nbuf;
		da_info.font_size = font_size;

		fzbuf = fz_new_buffer(ctx, 0);
		pdf_fzbuf_print_da(ctx, fzbuf, &da_info);

		da_len = fz_buffer_storage(ctx, fzbuf, &da_str);
		pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_DA, pdf_new_string(ctx, doc, (char *)da_str, da_len));

		/* FIXME: should convert to WinAnsiEncoding */
		pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_Contents, pdf_new_string(ctx, doc, text, strlen(text)));

		font_desc = pdf_load_font(ctx, doc, NULL, font, 0);
		pdf_measure_text(ctx, font_desc, (unsigned char *)text, strlen(text), &bounds);

		page_pos = *pos;
		fz_transform_point(&page_pos, &inv_page_ctm);

		bounds.x0 *= font_size;
		bounds.x1 *= font_size;
		bounds.y0 *= font_size;
		bounds.y1 *= font_size;

		bounds.x0 += page_pos.x;
		bounds.x1 += page_pos.x;
		bounds.y0 += page_pos.y;
		bounds.y1 += page_pos.y;

		pdf_dict_put_drop(ctx, annot->obj, PDF_NAME_Rect, pdf_new_rect(ctx, doc, &bounds));
	}
	fz_always(ctx)
	{
		pdf_drop_obj(ctx, font);
		fz_drop_buffer(ctx, fzbuf);
		pdf_drop_font(ctx, font_desc);
	}
	fz_catch(ctx)
	{
		fz_rethrow(ctx);
	}
}
Example #29
0
static void
svg_dev_end_tile(fz_context *ctx, fz_device *dev)
{
	svg_device *sdev = (svg_device*)dev;
	fz_output *out = sdev->out;
	int num, cp = -1;
	tile *t;
	fz_matrix inverse;
	float x, y, w, h;

	if (sdev->num_tiles == 0)
		return;
	num = --sdev->num_tiles;
	t = &sdev->tiles[num];

	fz_printf(ctx, out, "</symbol>\n");

	/* In svg, the reference tile is taken from (x,y) to (x+width,y+height)
	 * and is repeated at (x+n*width,y+m*height) for all integer n and m.
	 * This means that width and height generally correspond to xstep and
	 * ystep. There are exceptional cases where we have to break this
	 * though; when xstep/ystep are smaller than the width/height of the
	 * pattern tile, we need to render the pattern contents several times
	 * to ensure that the pattern tile contains everything. */

	fz_printf(ctx, out, "<pattern id=\"pa%d\" patternUnits=\"userSpaceOnUse\" patternContentUnits=\"userSpaceOnUse\"",
		t->pattern);
	fz_printf(ctx, out, " x=\"0\" y=\"0\" width=\"%g\" height=\"%g\">\n",
		t->step.x, t->step.y);

	if (t->view.x0 > 0 || t->step.x < t->view.x1 || t->view.y0 > 0 || t->step.y < t->view.y1)
	{
		cp = sdev->id++;
		fz_printf(ctx, out, "<clipPath id=\"cp%d\">\n", cp);
		fz_printf(ctx, out, "<path d=\"M %g %g L %g %g L %g %g L %g %g Z\"/>",
			t->view.x0, t->view.y0,
			t->view.x1, t->view.y0,
			t->view.x1, t->view.y1,
			t->view.x0, t->view.y1);
		fz_printf(ctx, out, "</clipPath>\n");
		fz_printf(ctx, out, "<g clip-path=\"url(#cp%d)\">\n", cp);
	}

	/* All the pattern contents will have their own ctm applied. Let's
	 * undo the current one to allow for this */
	fz_invert_matrix(&inverse, &t->ctm);
	fz_printf(ctx, out, "<g");
	svg_dev_ctm(ctx, sdev, &inverse);
	fz_printf(ctx, out, ">\n");

	w = t->view.x1 - t->view.x0;
	h = t->view.y1 - t->view.y0;

	for (x = 0; x > -w; x -= t->step.x)
		for (y = 0; y > -h; y -= t->step.y)
			fz_printf(ctx, out, "<use x=\"%g\" y=\"%g\" xlink:href=\"#pac%d\"/>", x, y, t->pattern);

	fz_printf(ctx, out, "</g>\n");
	if (cp != -1)
		fz_printf(ctx, out, "</g>\n");
	fz_printf(ctx, out, "</pattern>\n");
	out = end_def(ctx, sdev);

	/* Finally, fill a rectangle with the pattern. */
	fz_printf(ctx, out, "<rect");
	svg_dev_ctm(ctx, sdev, &t->ctm);
	fz_printf(ctx, out, " fill=\"url(#pa%d)\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"/>\n",
		t->pattern, t->area.x0, t->area.y0, t->area.x1 - t->area.x0, t->area.y1 - t->area.y0);
}
Example #30
0
static void
svg_dev_text(fz_context *ctx, svg_device *sdev, const fz_matrix *ctm, fz_text *text)
{
	fz_output *out = sdev->out;

	int i;
	fz_matrix inverse;
	fz_matrix local_trm;
	float size;
	int start, is_wspace, was_wspace;
	fz_text_span *span;

	for (span = text->head; text; span = span->next)
	{
		/* Rely on the fact that trm.{e,f} == 0 */
		size = fz_matrix_expansion(&span->trm);
		local_trm.a = span->trm.a / size;
		local_trm.b = span->trm.b / size;
		local_trm.c = -span->trm.c / size;
		local_trm.d = -span->trm.d / size;
		local_trm.e = 0;
		local_trm.f = 0;
		fz_invert_matrix(&inverse, &local_trm);
		fz_concat(&local_trm, &local_trm, ctm);

		fz_printf(ctx, out, " transform=\"matrix(%g,%g,%g,%g,%g,%g)\"",
				local_trm.a, local_trm.b, local_trm.c, local_trm.d, local_trm.e, local_trm.f);
		fz_printf(ctx, out, " font-size=\"%g\"", size);
		fz_printf(ctx, out, " font-family=\"%s\"", span->font->name);

		/* Leading (and repeated) whitespace presents a problem for SVG
		 * text, so elide it here. */
		for (start=0; start < span->len; start++)
		{
			fz_text_item *it = &span->items[start];
			if (!is_xml_wspace(it->ucs))
				break;
		}

		fz_printf(ctx, out, " x=");
		was_wspace = 0;
		for (i=start; i < span->len; i++)
		{
			fz_text_item *it = &span->items[i];
			fz_point p;
			is_wspace = is_xml_wspace(it->ucs);
			if (is_wspace && was_wspace)
				continue;
			was_wspace = is_wspace;
			p.x = it->x;
			p.y = it->y;
			fz_transform_point(&p, &inverse);
			fz_printf(ctx, out, "%c%g", i == start ? '\"' : ' ', p.x);
		}
		fz_printf(ctx, out, "\" y=");
		was_wspace = 0;
		for (i=start; i < span->len; i++)
		{
			fz_text_item *it = &span->items[i];
			fz_point p;
			is_wspace = is_xml_wspace(it->ucs);
			if (is_wspace && was_wspace)
				continue;
			was_wspace = is_wspace;
			p.x = it->x;
			p.y = it->y;
			fz_transform_point(&p, &inverse);
			fz_printf(ctx, out, "%c%g", i == start ? '\"' : ' ', p.y);
		}
		fz_printf(ctx, out, "\">\n");
		was_wspace = 0;
		for (i=start; i < span->len; i++)
		{
			fz_text_item *it = &span->items[i];
			int c = it->ucs;
			is_wspace = is_xml_wspace(c);
			if (is_wspace && was_wspace)
				continue;
			was_wspace = is_wspace;
			if (c >= 32 && c <= 127 && c != '<' && c != '&')
				fz_printf(ctx, out, "%c", c);
			else
				fz_printf(ctx, out, "&#x%04x;", c);
		}
		fz_printf(ctx, out, "\n</text>\n");
	}
}