From fa0bffa57e7e2164f0201b0f2fdc984d475674ae Mon Sep 17 00:00:00 2001
From: Tor Andersson <tor.andersson@artifex.com>
Date: Fri, 9 Oct 2015 11:07:42 +0200
Subject: Keep spans of multiple fonts and sizes in one fz_text object.

---
 source/fitz/draw-device.c   | 338 +++++++++++++++++++++++---------------------
 source/fitz/stext-device.c  |  76 ++++++----
 source/fitz/svg-device.c    | 214 +++++++++++++++-------------
 source/fitz/text.c          | 165 ++++++++++++---------
 source/fitz/trace-device.c  |  76 +++++-----
 source/html/html-layout.c   |  50 +++----
 source/pdf/pdf-appearance.c |  58 ++++----
 source/pdf/pdf-device.c     | 122 +++++++++-------
 source/pdf/pdf-op-run.c     |  24 +---
 source/xps/xps-glyphs.c     |  13 +-
 10 files changed, 616 insertions(+), 520 deletions(-)

(limited to 'source')

diff --git a/source/fitz/draw-device.c b/source/fitz/draw-device.c
index b109e6b7..ab6ebbe7 100644
--- a/source/fitz/draw-device.c
+++ b/source/fitz/draw-device.c
@@ -562,15 +562,13 @@ fz_draw_fill_text(fz_context *ctx, fz_device *devp, fz_text *text, const fz_matr
 	fz_colorspace *colorspace, float *color, float alpha)
 {
 	fz_draw_device *dev = (fz_draw_device*)devp;
-
+	fz_draw_state *state = &dev->stack[dev->top];
+	fz_colorspace *model = state->dest->colorspace;
 	unsigned char colorbv[FZ_MAX_COLORS + 1];
 	unsigned char shapebv;
 	float colorfv[FZ_MAX_COLORS];
-	fz_matrix tm, trm;
-	fz_glyph *glyph;
-	int i, gid;
-	fz_draw_state *state = &dev->stack[dev->top];
-	fz_colorspace *model = state->dest->colorspace;
+	fz_text_span *span;
+	int i;
 
 	if (state->blendmode & FZ_BLEND_KNOCKOUT)
 		state = fz_knockout_begin(ctx, dev);
@@ -581,50 +579,57 @@ fz_draw_fill_text(fz_context *ctx, fz_device *devp, fz_text *text, const fz_matr
 	colorbv[i] = alpha * 255;
 	shapebv = 255;
 
-	tm = text->trm;
-
-	for (i = 0; i < text->len; i++)
+	for (span = text->head; span; span = span->next)
 	{
-		gid = text->items[i].gid;
-		if (gid < 0)
-			continue;
+		fz_matrix tm, trm;
+		fz_glyph *glyph;
+		int gid;
 
-		tm.e = text->items[i].x;
-		tm.f = text->items[i].y;
-		fz_concat(&trm, &tm, ctm);
+		tm = span->trm;
 
-		glyph = fz_render_glyph(ctx, text->font, gid, &trm, model, &state->scissor);
-		if (glyph)
-		{
-			fz_pixmap *pixmap = glyph->pixmap;
-			int x = floorf(trm.e);
-			int y = floorf(trm.f);
-			if (pixmap == NULL || pixmap->n == 1)
-			{
-				draw_glyph(colorbv, state->dest, glyph, x, y, &state->scissor);
-				if (state->shape)
-					draw_glyph(&shapebv, state->shape, glyph, x, y, &state->scissor);
-			}
-			else
-			{
-				fz_matrix mat;
-				mat.a = pixmap->w; mat.b = mat.c = 0; mat.d = pixmap->h;
-				mat.e = x + pixmap->x; mat.f = y + pixmap->y;
-				fz_paint_image(state->dest, &state->scissor, state->shape, pixmap, &mat, alpha * 255, !(devp->hints & FZ_DONT_INTERPOLATE_IMAGES), devp->flags & FZ_DEVFLAG_GRIDFIT_AS_TILED);
-			}
-			fz_drop_glyph(ctx, glyph);
-		}
-		else
+		for (i = 0; i < span->len; i++)
 		{
-			fz_path *path = fz_outline_glyph(ctx, text->font, gid, &tm);
-			if (path)
+			gid = span->items[i].gid;
+			if (gid < 0)
+				continue;
+
+			tm.e = span->items[i].x;
+			tm.f = span->items[i].y;
+			fz_concat(&trm, &tm, ctm);
+
+			glyph = fz_render_glyph(ctx, span->font, gid, &trm, model, &state->scissor);
+			if (glyph)
 			{
-				fz_draw_fill_path(ctx, devp, path, 0, ctm, colorspace, color, alpha);
-				fz_drop_path(ctx, path);
+				fz_pixmap *pixmap = glyph->pixmap;
+				int x = floorf(trm.e);
+				int y = floorf(trm.f);
+				if (pixmap == NULL || pixmap->n == 1)
+				{
+					draw_glyph(colorbv, state->dest, glyph, x, y, &state->scissor);
+					if (state->shape)
+						draw_glyph(&shapebv, state->shape, glyph, x, y, &state->scissor);
+				}
+				else
+				{
+					fz_matrix mat;
+					mat.a = pixmap->w; mat.b = mat.c = 0; mat.d = pixmap->h;
+					mat.e = x + pixmap->x; mat.f = y + pixmap->y;
+					fz_paint_image(state->dest, &state->scissor, state->shape, pixmap, &mat, alpha * 255, !(devp->hints & FZ_DONT_INTERPOLATE_IMAGES), devp->flags & FZ_DEVFLAG_GRIDFIT_AS_TILED);
+				}
+				fz_drop_glyph(ctx, glyph);
 			}
 			else
 			{
-				fz_warn(ctx, "cannot render glyph");
+				fz_path *path = fz_outline_glyph(ctx, span->font, gid, &tm);
+				if (path)
+				{
+					fz_draw_fill_path(ctx, devp, path, 0, ctm, colorspace, color, alpha);
+					fz_drop_path(ctx, path);
+				}
+				else
+				{
+					fz_warn(ctx, "cannot render glyph");
+				}
 			}
 		}
 	}
@@ -639,14 +644,12 @@ fz_draw_stroke_text(fz_context *ctx, fz_device *devp, fz_text *text, fz_stroke_s
 	float *color, float alpha)
 {
 	fz_draw_device *dev = (fz_draw_device*)devp;
-
-	unsigned char colorbv[FZ_MAX_COLORS + 1];
-	float colorfv[FZ_MAX_COLORS];
-	fz_matrix tm, trm;
-	fz_glyph *glyph;
-	int i, gid;
 	fz_draw_state *state = &dev->stack[dev->top];
 	fz_colorspace *model = state->dest->colorspace;
+	unsigned char colorbv[FZ_MAX_COLORS + 1];
+	float colorfv[FZ_MAX_COLORS];
+	fz_text_span *span;
+	int i;
 
 	if (state->blendmode & FZ_BLEND_KNOCKOUT)
 		state = fz_knockout_begin(ctx, dev);
@@ -656,39 +659,46 @@ fz_draw_stroke_text(fz_context *ctx, fz_device *devp, fz_text *text, fz_stroke_s
 		colorbv[i] = colorfv[i] * 255;
 	colorbv[i] = alpha * 255;
 
-	tm = text->trm;
-
-	for (i = 0; i < text->len; i++)
+	for (span = text->head; span; span = span->next)
 	{
-		gid = text->items[i].gid;
-		if (gid < 0)
-			continue;
+		fz_matrix tm, trm;
+		fz_glyph *glyph;
+		int gid;
 
-		tm.e = text->items[i].x;
-		tm.f = text->items[i].y;
-		fz_concat(&trm, &tm, ctm);
+		tm = span->trm;
 
-		glyph = fz_render_stroked_glyph(ctx, text->font, gid, &trm, ctm, stroke, &state->scissor);
-		if (glyph)
-		{
-			int x = (int)trm.e;
-			int y = (int)trm.f;
-			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_glyph(ctx, glyph);
-		}
-		else
+		for (i = 0; i < span->len; i++)
 		{
-			fz_path *path = fz_outline_glyph(ctx, text->font, gid, &tm);
-			if (path)
+			gid = span->items[i].gid;
+			if (gid < 0)
+				continue;
+
+			tm.e = span->items[i].x;
+			tm.f = span->items[i].y;
+			fz_concat(&trm, &tm, ctm);
+
+			glyph = fz_render_stroked_glyph(ctx, span->font, gid, &trm, ctm, stroke, &state->scissor);
+			if (glyph)
 			{
-				fz_draw_stroke_path(ctx, devp, path, stroke, ctm, colorspace, color, alpha);
-				fz_drop_path(ctx, path);
+				int x = (int)trm.e;
+				int y = (int)trm.f;
+				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_glyph(ctx, glyph);
 			}
 			else
 			{
-				fz_warn(ctx, "cannot render glyph");
+				fz_path *path = fz_outline_glyph(ctx, span->font, gid, &tm);
+				if (path)
+				{
+					fz_draw_stroke_path(ctx, devp, path, stroke, ctm, colorspace, color, alpha);
+					fz_drop_path(ctx, path);
+				}
+				else
+				{
+					fz_warn(ctx, "cannot render glyph");
+				}
 			}
 		}
 	}
@@ -708,6 +718,7 @@ fz_draw_clip_text(fz_context *ctx, fz_device *devp, fz_text *text, const fz_matr
 	int i, gid;
 	fz_draw_state *state;
 	fz_colorspace *model;
+	fz_text_span *span;
 
 	/* 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 */
@@ -765,58 +776,61 @@ fz_draw_clip_text(fz_context *ctx, fz_device *devp, fz_text *text, const fz_matr
 
 		if (!fz_is_empty_irect(&bbox) && mask)
 		{
-			tm = text->trm;
-
-			for (i = 0; i < text->len; i++)
+			for (span = text->head; span; span = span->next)
 			{
-				gid = text->items[i].gid;
-				if (gid < 0)
-					continue;
+				tm = span->trm;
 
-				tm.e = text->items[i].x;
-				tm.f = text->items[i].y;
-				fz_concat(&trm, &tm, ctm);
-
-				glyph = fz_render_glyph(ctx, text->font, gid, &trm, model, &state->scissor);
-				if (glyph)
+				for (i = 0; i < span->len; i++)
 				{
-					int x = (int)trm.e;
-					int y = (int)trm.f;
-					draw_glyph(NULL, mask, glyph, x, y, &bbox);
-					if (state[1].shape)
-						draw_glyph(NULL, state[1].shape, glyph, x, y, &bbox);
-					fz_drop_glyph(ctx, glyph);
-				}
-				else
-				{
-					fz_path *path = fz_outline_glyph(ctx, text->font, gid, &tm);
-					if (path)
-					{
-						fz_pixmap *old_dest;
-						float white = 1;
+					gid = span->items[i].gid;
+					if (gid < 0)
+						continue;
 
-						old_dest = state[1].dest;
-						state[1].dest = state[1].mask;
-						state[1].mask = NULL;
-						fz_try(ctx)
-						{
-							fz_draw_fill_path(ctx, devp, path, 0, ctm, fz_device_gray(ctx), &white, 1);
-						}
-						fz_always(ctx)
+					tm.e = span->items[i].x;
+					tm.f = span->items[i].y;
+					fz_concat(&trm, &tm, ctm);
+
+					glyph = fz_render_glyph(ctx, span->font, gid, &trm, model, &state->scissor);
+					if (glyph)
+					{
+						int x = (int)trm.e;
+						int y = (int)trm.f;
+						draw_glyph(NULL, mask, glyph, x, y, &bbox);
+						if (state[1].shape)
+							draw_glyph(NULL, state[1].shape, glyph, x, y, &bbox);
+						fz_drop_glyph(ctx, glyph);
+					}
+					else
+					{
+						fz_path *path = fz_outline_glyph(ctx, span->font, gid, &tm);
+						if (path)
 						{
-							state[1].mask = state[1].dest;
-							state[1].dest = old_dest;
-							fz_drop_path(ctx, path);
+							fz_pixmap *old_dest;
+							float white = 1;
+
+							old_dest = state[1].dest;
+							state[1].dest = state[1].mask;
+							state[1].mask = NULL;
+							fz_try(ctx)
+							{
+								fz_draw_fill_path(ctx, devp, path, 0, ctm, fz_device_gray(ctx), &white, 1);
+							}
+							fz_always(ctx)
+							{
+								state[1].mask = state[1].dest;
+								state[1].dest = old_dest;
+								fz_drop_path(ctx, path);
+							}
+							fz_catch(ctx)
+							{
+								fz_rethrow(ctx);
+							}
 						}
-						fz_catch(ctx)
+						else
 						{
-							fz_rethrow(ctx);
+							fz_warn(ctx, "cannot render glyph for clipping");
 						}
 					}
-					else
-					{
-						fz_warn(ctx, "cannot render glyph for clipping");
-					}
 				}
 			}
 		}
@@ -840,6 +854,7 @@ fz_draw_clip_stroke_text(fz_context *ctx, fz_device *devp, fz_text *text, fz_str
 	int i, gid;
 	fz_draw_state *state = push_stack(ctx, dev);
 	fz_colorspace *model = state->dest->colorspace;
+	fz_text_span *span;
 	fz_rect rect;
 
 	STACK_PUSHED("clip stroke text");
@@ -869,59 +884,62 @@ fz_draw_clip_stroke_text(fz_context *ctx, fz_device *devp, fz_text *text, fz_str
 
 		if (!fz_is_empty_irect(&bbox))
 		{
-			tm = text->trm;
-
-			for (i = 0; i < text->len; i++)
+			for (span = text->head; span; span = span->next)
 			{
-				gid = text->items[i].gid;
-				if (gid < 0)
-					continue;
-
-				tm.e = text->items[i].x;
-				tm.f = text->items[i].y;
-				fz_concat(&trm, &tm, ctm);
+				tm = span->trm;
 
-				glyph = fz_render_stroked_glyph(ctx, text->font, gid, &trm, ctm, stroke, &state->scissor);
-				if (glyph)
-				{
-					int x = (int)trm.e;
-					int y = (int)trm.f;
-					draw_glyph(NULL, mask, glyph, x, y, &bbox);
-					if (shape)
-						draw_glyph(NULL, shape, glyph, x, y, &bbox);
-					fz_drop_glyph(ctx, glyph);
-				}
-				else
+				for (i = 0; i < span->len; i++)
 				{
-					fz_path *path = fz_outline_glyph(ctx, text->font, gid, &tm);
-					if (path)
+					gid = span->items[i].gid;
+					if (gid < 0)
+						continue;
+
+					tm.e = span->items[i].x;
+					tm.f = span->items[i].y;
+					fz_concat(&trm, &tm, ctm);
+
+					glyph = fz_render_stroked_glyph(ctx, span->font, gid, &trm, ctm, stroke, &state->scissor);
+					if (glyph)
 					{
-						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_stroke_path(ctx, devp, path, stroke, ctm, fz_device_gray(ctx), &white, 1);
-						}
-						fz_always(ctx)
+						int x = (int)trm.e;
+						int y = (int)trm.f;
+						draw_glyph(NULL, mask, glyph, x, y, &bbox);
+						if (shape)
+							draw_glyph(NULL, shape, glyph, x, y, &bbox);
+						fz_drop_glyph(ctx, glyph);
+					}
+					else
+					{
+						fz_path *path = fz_outline_glyph(ctx, span->font, gid, &tm);
+						if (path)
 						{
-							state[0].mask = state[0].dest;
-							state[0].dest = old_dest;
-							fz_drop_path(ctx, 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_stroke_path(ctx, devp, path, stroke, ctm, fz_device_gray(ctx), &white, 1);
+							}
+							fz_always(ctx)
+							{
+								state[0].mask = state[0].dest;
+								state[0].dest = old_dest;
+								fz_drop_path(ctx, path);
+							}
+							fz_catch(ctx)
+							{
+								fz_rethrow(ctx);
+							}
 						}
-						fz_catch(ctx)
+						else
 						{
-							fz_rethrow(ctx);
+							fz_warn(ctx, "cannot render glyph for stroked clipping");
 						}
 					}
-					else
-					{
-						fz_warn(ctx, "cannot render glyph for stroked clipping");
-					}
 				}
 			}
 		}
diff --git a/source/fitz/stext-device.c b/source/fitz/stext-device.c
index 4e14b298..060311d3 100644
--- a/source/fitz/stext-device.c
+++ b/source/fitz/stext-device.c
@@ -437,15 +437,15 @@ fz_lookup_stext_style_imp(fz_context *ctx, fz_stext_sheet *sheet,
 }
 
 static fz_stext_style *
-fz_lookup_stext_style(fz_context *ctx, fz_stext_sheet *sheet, fz_text *text, const fz_matrix *ctm,
+fz_lookup_stext_style(fz_context *ctx, fz_stext_sheet *sheet, fz_text_span *span, const fz_matrix *ctm,
 	fz_colorspace *colorspace, float *color, float alpha, fz_stroke_state *stroke)
 {
 	float size = 1.0f;
-	fz_font *font = text ? text->font : NULL;
-	int wmode = text ? text->wmode : 0;
-	if (ctm && text)
+	fz_font *font = span ? span->font : NULL;
+	int wmode = span ? span->wmode : 0;
+	if (ctm && span)
 	{
-		fz_matrix tm = text->trm;
+		fz_matrix tm = span->trm;
 		fz_matrix trm;
 		tm.e = 0;
 		tm.f = 0;
@@ -744,11 +744,11 @@ fz_add_stext_char(fz_context *ctx, fz_stext_device *dev, fz_stext_style *style,
 }
 
 static void
-fz_stext_extract(fz_context *ctx, fz_stext_device *dev, fz_text *text, const fz_matrix *ctm, fz_stext_style *style)
+fz_stext_extract(fz_context *ctx, fz_stext_device *dev, fz_text_span *span, const fz_matrix *ctm, fz_stext_style *style)
 {
-	fz_font *font = text->font;
+	fz_font *font = span->font;
 	FT_Face face = font->ft_face;
-	fz_matrix tm = text->trm;
+	fz_matrix tm = span->trm;
 	fz_matrix trm;
 	float adv;
 	float ascender = 1;
@@ -756,7 +756,7 @@ fz_stext_extract(fz_context *ctx, fz_stext_device *dev, fz_text *text, const fz_
 	int multi;
 	int i, j, err;
 
-	if (text->len == 0)
+	if (span->len == 0)
 		return;
 
 	if (dev->spans == NULL)
@@ -792,36 +792,36 @@ fz_stext_extract(fz_context *ctx, fz_stext_device *dev, fz_text *text, const fz_
 	tm.f = 0;
 	fz_concat(&trm, &tm, ctm);
 
-	for (i = 0; i < text->len; i++)
+	for (i = 0; i < span->len; i++)
 	{
 		/* Calculate new pen location and delta */
-		tm.e = text->items[i].x;
-		tm.f = text->items[i].y;
+		tm.e = span->items[i].x;
+		tm.f = span->items[i].y;
 		fz_concat(&trm, &tm, ctm);
 
 		/* Calculate bounding box and new pen position based on font metrics */
-		adv = fz_advance_glyph(ctx, font, text->items[i].gid);
+		adv = fz_advance_glyph(ctx, font, span->items[i].gid);
 
 		/* Check for one glyph to many char mapping */
-		for (j = i + 1; j < text->len; j++)
-			if (text->items[j].gid >= 0)
+		for (j = i + 1; j < span->len; j++)
+			if (span->items[j].gid >= 0)
 				break;
 		multi = j - i;
 
 		if (multi == 1)
 		{
-			fz_add_stext_char(ctx, dev, style, text->items[i].ucs, &trm, adv, text->wmode);
+			fz_add_stext_char(ctx, dev, style, span->items[i].ucs, &trm, adv, span->wmode);
 		}
 		else
 		{
 			for (j = 0; j < multi; j++)
 			{
-				fz_add_stext_char(ctx, dev, style, text->items[i + j].ucs, &trm, adv/multi, text->wmode);
+				fz_add_stext_char(ctx, dev, style, span->items[i + j].ucs, &trm, adv/multi, span->wmode);
 			}
 			i += j - 1;
 		}
 
-		dev->lastchar = text->items[i].ucs;
+		dev->lastchar = span->items[i].ucs;
 	}
 }
 
@@ -831,8 +831,12 @@ fz_stext_fill_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matr
 {
 	fz_stext_device *tdev = (fz_stext_device*)dev;
 	fz_stext_style *style;
-	style = fz_lookup_stext_style(ctx, tdev->sheet, text, ctm, colorspace, color, alpha, NULL);
-	fz_stext_extract(ctx, tdev, text, ctm, style);
+	fz_text_span *span;
+	for (span = text->head; span; span = span->next)
+	{
+		style = fz_lookup_stext_style(ctx, tdev->sheet, span, ctm, colorspace, color, alpha, NULL);
+		fz_stext_extract(ctx, tdev, span, ctm, style);
+	}
 }
 
 static void
@@ -841,8 +845,12 @@ fz_stext_stroke_text(fz_context *ctx, fz_device *dev, fz_text *text, fz_stroke_s
 {
 	fz_stext_device *tdev = (fz_stext_device*)dev;
 	fz_stext_style *style;
-	style = fz_lookup_stext_style(ctx, tdev->sheet, text, ctm, colorspace, color, alpha, stroke);
-	fz_stext_extract(ctx, tdev, text, ctm, style);
+	fz_text_span *span;
+	for (span = text->head; span; span = span->next)
+	{
+		style = fz_lookup_stext_style(ctx, tdev->sheet, span, ctm, colorspace, color, alpha, stroke);
+		fz_stext_extract(ctx, tdev, span, ctm, style);
+	}
 }
 
 static void
@@ -850,8 +858,12 @@ fz_stext_clip_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matr
 {
 	fz_stext_device *tdev = (fz_stext_device*)dev;
 	fz_stext_style *style;
-	style = fz_lookup_stext_style(ctx, tdev->sheet, text, ctm, NULL, NULL, 0, NULL);
-	fz_stext_extract(ctx, tdev, text, ctm, style);
+	fz_text_span *span;
+	for (span = text->head; span; span = span->next)
+	{
+		style = fz_lookup_stext_style(ctx, tdev->sheet, span, ctm, NULL, NULL, 0, NULL);
+		fz_stext_extract(ctx, tdev, span, ctm, style);
+	}
 }
 
 static void
@@ -859,8 +871,12 @@ fz_stext_clip_stroke_text(fz_context *ctx, fz_device *dev, fz_text *text, fz_str
 {
 	fz_stext_device *tdev = (fz_stext_device*)dev;
 	fz_stext_style *style;
-	style = fz_lookup_stext_style(ctx, tdev->sheet, text, ctm, NULL, NULL, 0, stroke);
-	fz_stext_extract(ctx, tdev, text, ctm, style);
+	fz_text_span *span;
+	for (span = text->head; span; span = span->next)
+	{
+		style = fz_lookup_stext_style(ctx, tdev->sheet, span, ctm, NULL, NULL, 0, stroke);
+		fz_stext_extract(ctx, tdev, span, ctm, style);
+	}
 }
 
 static void
@@ -868,8 +884,12 @@ fz_stext_ignore_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_ma
 {
 	fz_stext_device *tdev = (fz_stext_device*)dev;
 	fz_stext_style *style;
-	style = fz_lookup_stext_style(ctx, tdev->sheet, text, ctm, NULL, NULL, 0, NULL);
-	fz_stext_extract(ctx, tdev, text, ctm, style);
+	fz_text_span *span;
+	for (span = text->head; span; span = span->next)
+	{
+		style = fz_lookup_stext_style(ctx, tdev->sheet, span, ctm, NULL, NULL, 0, NULL);
+		fz_stext_extract(ctx, tdev, span, ctm, style);
+	}
 }
 
 static void
diff --git a/source/fitz/svg-device.c b/source/fitz/svg-device.c
index 4461f79e..a2cc5ed1 100644
--- a/source/fitz/svg-device.c
+++ b/source/fitz/svg-device.c
@@ -238,82 +238,86 @@ svg_dev_text(fz_context *ctx, svg_device *sdev, const fz_matrix *ctm, fz_text *t
 	fz_matrix local_trm;
 	float size;
 	int start, is_wspace, was_wspace;
+	fz_text_span *span;
 
-	/* 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);
+	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\"", text->font->name);
+		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 < text->len; start++)
-	{
-		fz_text_item *it = &text->items[start];
-		if (!is_xml_wspace(it->ucs))
-			break;
-	}
+		/* 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 < text->len; i++)
-	{
-		fz_text_item *it = &text->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 < text->len; i++)
-	{
-		fz_text_item *it = &text->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 < text->len; i++)
-	{
-		fz_text_item *it = &text->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, " 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");
 	}
-	fz_printf(ctx, out, "\n</text>\n");
 }
 
 static font *
-svg_dev_text_as_paths_defs(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matrix *ctm)
+svg_dev_text_span_as_paths_defs(fz_context *ctx, fz_device *dev, fz_text_span *span, const fz_matrix *ctm)
 {
 	svg_device *sdev = (svg_device*)dev;
 	fz_output *out = sdev->out;
@@ -323,7 +327,7 @@ svg_dev_text_as_paths_defs(fz_context *ctx, fz_device *dev, fz_text *text, const
 
 	for (font_idx = 0; font_idx < sdev->num_fonts; font_idx++)
 	{
-		if (sdev->fonts[font_idx].font == text->font)
+		if (sdev->fonts[font_idx].font == span->font)
 			break;
 	}
 	if (font_idx == sdev->num_fonts)
@@ -339,14 +343,14 @@ svg_dev_text_as_paths_defs(fz_context *ctx, fz_device *dev, fz_text *text, const
 			sdev->max_fonts = newmax;
 		}
 		sdev->fonts[font_idx].id = sdev->id++;
-		sdev->fonts[font_idx].font = fz_keep_font(ctx, text->font);
+		sdev->fonts[font_idx].font = fz_keep_font(ctx, span->font);
 		sdev->num_fonts++;
 	}
 	fnt = &sdev->fonts[font_idx];
 
-	for (i=0; i < text->len; i++)
+	for (i=0; i < span->len; i++)
 	{
-		fz_text_item *it = &text->items[i];
+		fz_text_item *it = &span->items[i];
 		int gid = it->gid;
 
 		if (gid < 0)
@@ -367,7 +371,7 @@ svg_dev_text_as_paths_defs(fz_context *ctx, fz_device *dev, fz_text *text, const
 			/* Need to send this one */
 			fz_rect rect;
 			fz_path *path;
-			path = fz_outline_glyph(ctx, text->font, gid, &fz_identity);
+			path = fz_outline_glyph(ctx, span->font, gid, &fz_identity);
 			if (path)
 			{
 				fz_bound_path(ctx, path, NULL, &fz_identity, &rect);
@@ -382,12 +386,12 @@ svg_dev_text_as_paths_defs(fz_context *ctx, fz_device *dev, fz_text *text, const
 			}
 			else
 			{
-				fz_bound_glyph(ctx, text->font, gid, &fz_identity, &rect);
+				fz_bound_glyph(ctx, span->font, gid, &fz_identity, &rect);
 				shift.e = -rect.x0;
 				shift.f = -rect.y0;
 				out = start_def(ctx, sdev);
 				fz_printf(ctx, out, "<symbol id=\"font_%x_%x\">", fnt->id, gid);
-				fz_run_t3_glyph(ctx, text->font, gid, &shift, dev);
+				fz_run_t3_glyph(ctx, span->font, gid, &shift, dev);
 			}
 			fz_printf(ctx, out, "</symbol>");
 			out = end_def(ctx, sdev);
@@ -399,7 +403,7 @@ svg_dev_text_as_paths_defs(fz_context *ctx, fz_device *dev, fz_text *text, const
 }
 
 static void
-svg_dev_text_as_paths_fill(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matrix *ctm,
+svg_dev_text_span_as_paths_fill(fz_context *ctx, fz_device *dev, fz_text_span *span, const fz_matrix *ctm,
 	fz_colorspace *colorspace, float *color, float alpha, font *fnt)
 {
 	svg_device *sdev = (svg_device*)dev;
@@ -410,16 +414,16 @@ svg_dev_text_as_paths_fill(fz_context *ctx, fz_device *dev, fz_text *text, const
 	fz_matrix shift = { 1, 0, 0, 1, 0, 0};
 
 	/* Rely on the fact that trm.{e,f} == 0 */
-	local_trm.a = text->trm.a;
-	local_trm.b = text->trm.b;
-	local_trm.c = text->trm.c;
-	local_trm.d = text->trm.d;
+	local_trm.a = span->trm.a;
+	local_trm.b = span->trm.b;
+	local_trm.c = span->trm.c;
+	local_trm.d = span->trm.d;
 	local_trm.e = 0;
 	local_trm.f = 0;
 
-	for (i=0; i < text->len; i++)
+	for (i=0; i < span->len; i++)
 	{
-		fz_text_item *it = &text->items[i];
+		fz_text_item *it = &span->items[i];
 		int gid = it->gid;
 
 		if (gid < 0)
@@ -439,7 +443,7 @@ svg_dev_text_as_paths_fill(fz_context *ctx, fz_device *dev, fz_text *text, const
 }
 
 static void
-svg_dev_text_as_paths_stroke(fz_context *ctx, fz_device *dev, fz_text *text,
+svg_dev_text_span_as_paths_stroke(fz_context *ctx, fz_device *dev, fz_text_span *span,
 	fz_stroke_state *stroke, const fz_matrix *ctm,
 	fz_colorspace *colorspace, float *color, float alpha, font *fnt)
 {
@@ -451,16 +455,16 @@ svg_dev_text_as_paths_stroke(fz_context *ctx, fz_device *dev, fz_text *text,
 	fz_matrix shift = { 1, 0, 0, 1, 0, 0};
 
 	/* Rely on the fact that trm.{e,f} == 0 */
-	local_trm.a = text->trm.a;
-	local_trm.b = text->trm.b;
-	local_trm.c = text->trm.c;
-	local_trm.d = text->trm.d;
+	local_trm.a = span->trm.a;
+	local_trm.b = span->trm.b;
+	local_trm.c = span->trm.c;
+	local_trm.d = span->trm.d;
 	local_trm.e = 0;
 	local_trm.f = 0;
 
-	for (i=0; i < text->len; i++)
+	for (i=0; i < span->len; i++)
 	{
-		fz_text_item *it = &text->items[i];
+		fz_text_item *it = &span->items[i];
 		int gid = it->gid;
 
 		if (gid < 0)
@@ -566,12 +570,16 @@ svg_dev_fill_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matri
 	fz_output *out = sdev->out;
 
 	font *fnt;
+	fz_text_span *span;
 
 	fz_printf(ctx, out, "<text");
 	svg_dev_fill_color(ctx, sdev, colorspace, color, 0.0f);
 	svg_dev_text(ctx, sdev, ctm, text);
-	fnt = svg_dev_text_as_paths_defs(ctx, dev, text, ctm);
-	svg_dev_text_as_paths_fill(ctx, dev, text, ctm, colorspace, color, alpha, fnt);
+	for (span = text->head; span; span = span->next)
+	{
+		fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
+		svg_dev_text_span_as_paths_fill(ctx, dev, span, ctm, colorspace, color, alpha, fnt);
+	}
 }
 
 static void
@@ -582,12 +590,16 @@ svg_dev_stroke_text(fz_context *ctx, fz_device *dev, fz_text *text, fz_stroke_st
 	fz_output *out = sdev->out;
 
 	font *fnt;
+	fz_text_span *span;
 
 	fz_printf(ctx, out, "<text");
 	svg_dev_fill_color(ctx, sdev, colorspace, color, 0.0f);
 	svg_dev_text(ctx, sdev, ctm, text);
-	fnt = svg_dev_text_as_paths_defs(ctx, dev, text, ctm);
-	svg_dev_text_as_paths_stroke(ctx, dev, text, stroke, ctm, colorspace, color, alpha, fnt);
+	for (span = text->head; span; span = span->next)
+	{
+		fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
+		svg_dev_text_span_as_paths_stroke(ctx, dev, span, stroke, ctm, colorspace, color, alpha, fnt);
+	}
 }
 
 static void
@@ -600,6 +612,7 @@ svg_dev_clip_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matri
 	int num = sdev->id++;
 	float white[3] = { 1, 1, 1 };
 	font *fnt;
+	fz_text_span *span;
 
 	fz_bound_text(ctx, text, NULL, ctm, &bounds);
 
@@ -609,8 +622,11 @@ svg_dev_clip_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matri
 	fz_printf(ctx, out, "<text");
 	svg_dev_fill_color(ctx, sdev, fz_device_rgb(ctx), white, 0.0f);
 	svg_dev_text(ctx, sdev, ctm, text);
-	fnt = svg_dev_text_as_paths_defs(ctx, dev, text, ctm);
-	svg_dev_text_as_paths_fill(ctx, dev, text, ctm, fz_device_rgb(ctx), white, 1.0f, fnt);
+	for (span = text->head; span; span = span->next)
+	{
+		fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
+		svg_dev_text_span_as_paths_fill(ctx, dev, span, ctm, fz_device_rgb(ctx), white, 1.0f, fnt);
+	}
 	fz_printf(ctx, out, "</mask>\n");
 	out = end_def(ctx, sdev);
 	fz_printf(ctx, out, "<g mask=\"url(#ma%d)\">\n", num);
@@ -626,6 +642,7 @@ svg_dev_clip_stroke_text(fz_context *ctx, fz_device *dev, fz_text *text, fz_stro
 	int num = sdev->id++;
 	float white[3] = { 255, 255, 255 };
 	font *fnt;
+	fz_text_span *span;
 
 	fz_bound_text(ctx, text, NULL, ctm, &bounds);
 
@@ -636,8 +653,11 @@ svg_dev_clip_stroke_text(fz_context *ctx, fz_device *dev, fz_text *text, fz_stro
 	svg_dev_stroke_state(ctx, sdev, stroke, &fz_identity);
 	svg_dev_stroke_color(ctx, sdev, fz_device_rgb(ctx), white, 0.0f);
 	svg_dev_text(ctx, sdev, ctm, text);
-	fnt = svg_dev_text_as_paths_defs(ctx, dev, text, ctm);
-	svg_dev_text_as_paths_stroke(ctx, dev, text, stroke, ctm, fz_device_rgb(ctx), white, 1.0f, fnt);
+	for (span = text->head; span; span = span->next)
+	{
+		fnt = svg_dev_text_span_as_paths_defs(ctx, dev, span, ctm);
+		svg_dev_text_span_as_paths_stroke(ctx, dev, span, stroke, ctm, fz_device_rgb(ctx), white, 1.0f, fnt);
+	}
 	fz_printf(ctx, out, "</mask>\n");
 	out = end_def(ctx, sdev);
 	fz_printf(ctx, out, "<g mask=\"url(#ma%d)\">\n", num);
diff --git a/source/fitz/text.c b/source/fitz/text.c
index 0616d6a9..f90fee53 100644
--- a/source/fitz/text.c
+++ b/source/fitz/text.c
@@ -1,19 +1,10 @@
 #include "mupdf/fitz.h"
 
 fz_text *
-fz_new_text(fz_context *ctx, fz_font *font, const fz_matrix *trm, int wmode)
+fz_new_text(fz_context *ctx)
 {
-	fz_text *text;
-
-	text = fz_malloc_struct(ctx, fz_text);
+	fz_text *text = fz_malloc_struct(ctx, fz_text);
 	text->refs = 1;
-	text->font = fz_keep_font(ctx, font);
-	text->trm = *trm;
-	text->wmode = wmode;
-	text->len = 0;
-	text->cap = 0;
-	text->items = NULL;
-
 	return text;
 }
 
@@ -28,84 +19,122 @@ fz_drop_text(fz_context *ctx, fz_text *text)
 {
 	if (fz_drop_imp(ctx, text, &text->refs))
 	{
-		fz_drop_font(ctx, text->font);
-		fz_free(ctx, text->items);
+		fz_text_span *span = text->head;
+		while (span)
+		{
+			fz_text_span *next = span->next;
+			fz_drop_font(ctx, span->font);
+			fz_free(ctx, span->items);
+			fz_free(ctx, span);
+			span = next;
+		}
 		fz_free(ctx, text);
 	}
 }
 
-fz_rect *
-fz_bound_text(fz_context *ctx, fz_text *text, const fz_stroke_state *stroke, const fz_matrix *ctm, fz_rect *bbox)
+static fz_text_span *
+fz_new_text_span(fz_context *ctx, fz_font *font, int wmode, const fz_matrix *trm)
 {
-	fz_matrix tm, trm;
-	fz_rect gbox;
-	int i;
+	fz_text_span *span = fz_malloc_struct(ctx, fz_text_span);
+	span->font = fz_keep_font(ctx, font);
+	span->wmode = wmode;
+	span->trm = *trm;
+	span->trm.e = 0;
+	span->trm.f = 0;
+	return span;
+}
 
-	if (text->len == 0)
+static fz_text_span *
+fz_add_text_span(fz_context *ctx, fz_text *text, fz_font *font, int wmode, const fz_matrix *trm)
+{
+	if (!text->tail)
 	{
-		*bbox = fz_empty_rect;
-		return bbox;
+		text->head = text->tail = fz_new_text_span(ctx, font, wmode, trm);
 	}
-
-	// TODO: stroke state
-
-	tm = text->trm;
-
-	tm.e = text->items[0].x;
-	tm.f = text->items[0].y;
-	fz_concat(&trm, &tm, ctm);
-	fz_bound_glyph(ctx, text->font, text->items[0].gid, &trm, bbox);
-
-	for (i = 1; i < text->len; i++)
+	else if (text->tail->font != font ||
+		text->tail->wmode != wmode ||
+		text->tail->trm.a != trm->a ||
+		text->tail->trm.b != trm->b ||
+		text->tail->trm.c != trm->c ||
+		text->tail->trm.d != trm->d)
 	{
-		if (text->items[i].gid >= 0)
-		{
-			tm.e = text->items[i].x;
-			tm.f = text->items[i].y;
-			fz_concat(&trm, &tm, ctm);
-			fz_bound_glyph(ctx, text->font, text->items[i].gid, &trm, &gbox);
-
-			bbox->x0 = fz_min(bbox->x0, gbox.x0);
-			bbox->y0 = fz_min(bbox->y0, gbox.y0);
-			bbox->x1 = fz_max(bbox->x1, gbox.x1);
-			bbox->y1 = fz_max(bbox->y1, gbox.y1);
-		}
+		text->tail = text->tail->next = fz_new_text_span(ctx, font, wmode, trm);
 	}
-
-	if (stroke)
-		fz_adjust_rect_for_stroke(ctx, bbox, stroke, ctm);
-
-	/* Compensate for the glyph cache limited positioning precision */
-	bbox->x0 -= 1;
-	bbox->y0 -= 1;
-	bbox->x1 += 1;
-	bbox->y1 += 1;
-
-	return bbox;
+	return text->tail;
 }
 
 static void
-fz_grow_text(fz_context *ctx, fz_text *text, int n)
+fz_grow_text_span(fz_context *ctx, fz_text_span *span, int n)
 {
-	int new_cap = text->cap;
-	if (text->len + n < new_cap)
+	int new_cap = span->cap;
+	if (span->len + n < new_cap)
 		return;
-	while (text->len + n > new_cap)
+	while (span->len + n > new_cap)
 		new_cap = new_cap + 36;
-	text->items = fz_resize_array(ctx, text->items, new_cap, sizeof(fz_text_item));
-	text->cap = new_cap;
+	span->items = fz_resize_array(ctx, span->items, new_cap, sizeof(fz_text_item));
+	span->cap = new_cap;
 }
 
 void
-fz_add_text(fz_context *ctx, fz_text *text, int gid, int ucs, float x, float y)
+fz_add_text(fz_context *ctx, fz_text *text, fz_font *font, int wmode, const fz_matrix *trm, int gid, int ucs)
 {
+	fz_text_span *span;
+
 	if (text->refs != 1)
 		fz_throw(ctx, FZ_ERROR_GENERIC, "cannot modify shared text objects");
 
-	fz_grow_text(ctx, text, 1);
-	text->items[text->len].ucs = ucs;
-	text->items[text->len].gid = gid;
-	text->items[text->len].x = x;
-	text->items[text->len].y = y;
-	text->len++;
+	span = fz_add_text_span(ctx, text, font, wmode, trm);
+
+	fz_grow_text_span(ctx, span, 1);
+
+	span->items[span->len].ucs = ucs;
+	span->items[span->len].gid = gid;
+	span->items[span->len].x = trm->e;
+	span->items[span->len].y = trm->f;
+	span->len++;
+}
+
+fz_rect *
+fz_bound_text(fz_context *ctx, fz_text *text, const fz_stroke_state *stroke, const fz_matrix *ctm, fz_rect *bbox)
+{
+	fz_text_span *span;
+	fz_matrix tm, trm;
+	fz_rect gbox;
+	int i;
+
+	*bbox = fz_empty_rect;
+
+	for (span = text->head; span; span = span->next)
+	{
+		if (span->len > 0)
+		{
+			tm = span->trm;
+			for (i = 0; i < span->len; i++)
+			{
+				if (span->items[i].gid >= 0)
+				{
+					tm.e = span->items[i].x;
+					tm.f = span->items[i].y;
+					fz_concat(&trm, &tm, ctm);
+					fz_bound_glyph(ctx, span->font, span->items[i].gid, &trm, &gbox);
+					fz_union_rect(bbox, &gbox);
+				}
+			}
+
+		}
+	}
+
+	if (!fz_is_empty_rect(bbox))
+	{
+		if (stroke)
+			fz_adjust_rect_for_stroke(ctx, bbox, stroke, ctm);
+
+		/* Compensate for the glyph cache limited positioning precision */
+		bbox->x0 -= 1;
+		bbox->y0 -= 1;
+		bbox->x1 += 1;
+		bbox->y1 += 1;
+	}
+
+	return bbox;
 }
diff --git a/source/fitz/trace-device.c b/source/fitz/trace-device.c
index 59ea8ad8..df2f6455 100644
--- a/source/fitz/trace-device.c
+++ b/source/fitz/trace-device.c
@@ -13,13 +13,6 @@ fz_trace_matrix(fz_context *ctx, fz_output *out, const fz_matrix *ctm)
 		ctm->a, ctm->b, ctm->c, ctm->d, ctm->e, ctm->f);
 }
 
-static void
-fz_trace_trm(fz_context *ctx, fz_output *out, const fz_matrix *trm)
-{
-	fz_printf(ctx, out, " trm=\"%g %g %g %g\"",
-		trm->a, trm->b, trm->c, trm->d);
-}
-
 static void
 fz_trace_color(fz_context *ctx, fz_output *out, fz_colorspace *colorspace, float *color, float alpha)
 {
@@ -32,6 +25,38 @@ fz_trace_color(fz_context *ctx, fz_output *out, fz_colorspace *colorspace, float
 		fz_printf(ctx, out, " alpha=\"%g\"", alpha);
 }
 
+static int
+isxmlmeta(int c)
+{
+	return c < 32 || c >= 128 || c == '&' || c == '<' || c == '>' || c == '\'' || c == '"';
+}
+
+static void
+fz_trace_text_span(fz_context *ctx, fz_output *out, fz_text_span *span)
+{
+	int i;
+	fz_printf(ctx, out, "<span font=\"%s\" wmode=\"%d\"", span->font->name, span->wmode);
+	fz_printf(ctx, out, " trm=\"%g %g %g %g\">\n", span->trm.a, span->trm.b, span->trm.c, span->trm.d);
+	for (i = 0; i < span->len; i++)
+	{
+		if (!isxmlmeta(span->items[i].ucs))
+			fz_printf(ctx, out, "<g ucs=\"%c\" gid=\"%d\" x=\"%g\" y=\"%g\" />\n",
+					span->items[i].ucs, span->items[i].gid, span->items[i].x, span->items[i].y);
+		else
+			fz_printf(ctx, out, "<g ucs=\"U+%04X\" gid=\"%d\" x=\"%g\" y=\"%g\" />\n",
+					span->items[i].ucs, span->items[i].gid, span->items[i].x, span->items[i].y);
+	}
+	fz_printf(ctx, out, "</span>\n");
+}
+
+static void
+fz_trace_text(fz_context *ctx, fz_output *out, fz_text *text)
+{
+	fz_text_span *span;
+	for (span = text->head; span; span = span->next)
+		fz_trace_text_span(ctx, out, span);
+}
+
 static void
 trace_moveto(fz_context *ctx, void *arg, float x, float y)
 {
@@ -166,36 +191,14 @@ fz_trace_clip_stroke_path(fz_context *ctx, fz_device *dev, fz_path *path, const
 	fz_printf(ctx, out, "</clip_stroke_path>\n");
 }
 
-static int
-isxmlmeta(int c)
-{
-	return c < 32 || c >= 128 || c == '&' || c == '<' || c == '>' || c == '\'' || c == '"';
-}
-
-static void
-fz_trace_text(fz_context *ctx, fz_output *out, fz_text *text)
-{
-	int i;
-	for (i = 0; i < text->len; i++)
-	{
-		if (!isxmlmeta(text->items[i].ucs))
-			fz_printf(ctx, out, "<g ucs=\"%c\" gid=\"%d\" x=\"%g\" y=\"%g\" />\n",
-				text->items[i].ucs, text->items[i].gid, text->items[i].x, text->items[i].y);
-		else
-			fz_printf(ctx, out, "<g ucs=\"U+%04X\" gid=\"%d\" x=\"%g\" y=\"%g\" />\n",
-				text->items[i].ucs, text->items[i].gid, text->items[i].x, text->items[i].y);
-	}
-}
-
 static void
 fz_trace_fill_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matrix *ctm,
 	fz_colorspace *colorspace, float *color, float alpha)
 {
 	fz_output *out = ((fz_trace_device*)dev)->out;
-	fz_printf(ctx, out, "<fill_text font=\"%s\" wmode=\"%d\"", text->font->name, text->wmode);
+	fz_printf(ctx, out, "<fill_text");
 	fz_trace_color(ctx, out, colorspace, color, alpha);
 	fz_trace_matrix(ctx, out, ctm);
-	fz_trace_trm(ctx, out, &text->trm);
 	fz_printf(ctx, out, ">\n");
 	fz_trace_text(ctx, out, text);
 	fz_printf(ctx, out, "</fill_text>\n");
@@ -206,10 +209,9 @@ fz_trace_stroke_text(fz_context *ctx, fz_device *dev, fz_text *text, fz_stroke_s
 	fz_colorspace *colorspace, float *color, float alpha)
 {
 	fz_output *out = ((fz_trace_device*)dev)->out;
-	fz_printf(ctx, out, "<stroke_text font=\"%s\" wmode=\"%d\"", text->font->name, text->wmode);
+	fz_printf(ctx, out, "<stroke_text");
 	fz_trace_color(ctx, out, colorspace, color, alpha);
 	fz_trace_matrix(ctx, out, ctm);
-	fz_trace_trm(ctx, out, &text->trm);
 	fz_printf(ctx, out, ">\n");
 	fz_trace_text(ctx, out, text);
 	fz_printf(ctx, out, "</stroke_text>\n");
@@ -219,10 +221,8 @@ static void
 fz_trace_clip_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matrix *ctm, int accumulate)
 {
 	fz_output *out = ((fz_trace_device*)dev)->out;
-	fz_printf(ctx, out, "<clip_text font=\"%s\" wmode=\"%d\"", text->font->name, text->wmode);
-	fz_printf(ctx, out, " accumulate=\"%d\"", accumulate);
+	fz_printf(ctx, out, "<clip_text");
 	fz_trace_matrix(ctx, out, ctm);
-	fz_trace_trm(ctx, out, &text->trm);
 	fz_printf(ctx, out, ">\n");
 	fz_trace_text(ctx, out, text);
 	fz_printf(ctx, out, "</clip_text>\n");
@@ -232,9 +232,8 @@ static void
 fz_trace_clip_stroke_text(fz_context *ctx, fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
 {
 	fz_output *out = ((fz_trace_device*)dev)->out;
-	fz_printf(ctx, out, "<clip_stroke_text font=\"%s\" wmode=\"%d\"", text->font->name, text->wmode);
+	fz_printf(ctx, out, "<clip_stroke_text");
 	fz_trace_matrix(ctx, out, ctm);
-	fz_trace_trm(ctx, out, &text->trm);
 	fz_printf(ctx, out, ">\n");
 	fz_trace_text(ctx, out, text);
 	fz_printf(ctx, out, "</clip_stroke_text>\n");
@@ -244,9 +243,8 @@ static void
 fz_trace_ignore_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matrix *ctm)
 {
 	fz_output *out = ((fz_trace_device*)dev)->out;
-	fz_printf(ctx, out, "<ignore_text font=\"%s\" wmode=\"%d\"", text->font->name, text->wmode);
+	fz_printf(ctx, out, "<ignore_text");
 	fz_trace_matrix(ctx, out, ctm);
-	fz_trace_trm(ctx, out, &text->trm);
 	fz_printf(ctx, out, ">\n");
 	fz_trace_text(ctx, out, text);
 	fz_printf(ctx, out, "</ignore_text>\n");
diff --git a/source/html/html-layout.c b/source/html/html-layout.c
index f742969c..b7e2b779 100644
--- a/source/html/html-layout.c
+++ b/source/html/html-layout.c
@@ -825,11 +825,9 @@ static void draw_flow_box(fz_context *ctx, fz_html *box, float page_top, float p
 {
 	fz_html_flow *node;
 	fz_text *text;
-	fz_text *falltext;
 	fz_matrix trm;
 	const char *s;
 	float color[3];
-	float x, y;
 	int c, g;
 
 	for (node = box->flow_head; node; node = node->next)
@@ -853,11 +851,11 @@ static void draw_flow_box(fz_context *ctx, fz_html *box, float page_top, float p
 			color[1] = node->style->color.g / 255.0f;
 			color[2] = node->style->color.b / 255.0f;
 
-			text = NULL;
-			falltext = NULL;
+			/* TODO: reuse text object if color is unchanged */
+			text = fz_new_text(ctx);
 
-			x = node->x;
-			y = node->y;
+			trm.e = node->x;
+			trm.f = node->y;
 			s = node->text;
 			while (*s)
 			{
@@ -866,12 +864,8 @@ static void draw_flow_box(fz_context *ctx, fz_html *box, float page_top, float p
 				if (g)
 				{
 					if (node->style->visibility == V_VISIBLE)
-					{
-						if (!text)
-							text = fz_new_text(ctx, node->style->font, &trm, 0);
-						fz_add_text(ctx, text, g, c, x, y);
-					}
-					x += fz_advance_glyph(ctx, node->style->font, g) * node->em;
+						fz_add_text(ctx, text, node->style->font, 0, &trm, g, c);
+					trm.e += fz_advance_glyph(ctx, node->style->font, g) * node->em;
 				}
 				else
 				{
@@ -879,13 +873,16 @@ static void draw_flow_box(fz_context *ctx, fz_html *box, float page_top, float p
 					if (g)
 					{
 						if (node->style->visibility == V_VISIBLE)
-						{
-							if (!falltext)
-								falltext = fz_new_text(ctx, node->style->fallback, &trm, 0);
-							fz_add_text(ctx, falltext, g, c, x, y);
-						}
+							fz_add_text(ctx, text, node->style->fallback, 0, &trm, g, c);
+						trm.e += fz_advance_glyph(ctx, node->style->fallback, g) * node->em;
+					}
+					else
+					{
+						g = fz_encode_character(ctx, node->style->font, 0x25CF); /* bullet */
+						if (node->style->visibility == V_VISIBLE)
+							fz_add_text(ctx, text, node->style->font, 0, &trm, g, c);
+						trm.e += fz_advance_glyph(ctx, node->style->font, g) * node->em;
 					}
-					x += fz_advance_glyph(ctx, node->style->fallback, g) * node->em;
 				}
 			}
 
@@ -894,11 +891,6 @@ static void draw_flow_box(fz_context *ctx, fz_html *box, float page_top, float p
 				fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color, 1);
 				fz_drop_text(ctx, text);
 			}
-			if (falltext)
-			{
-				fz_fill_text(ctx, dev, falltext, ctm, fz_device_rgb(ctx), color, 1);
-				fz_drop_text(ctx, falltext);
-			}
 		}
 		else if (node->type == FLOW_IMAGE)
 		{
@@ -1039,14 +1031,15 @@ static void draw_list_mark(fz_context *ctx, fz_html *box, float page_top, float
 	fz_text *text;
 	fz_matrix trm;
 	fz_html_flow *line;
-	float x, y, w;
+	float y, w;
 	float color[3];
 	const char *s;
 	char buf[40];
 	int c, g;
 
 	fz_scale(&trm, box->em, -box->em);
-	text = fz_new_text(ctx, box->style.font, &trm, 0);
+
+	text = fz_new_text(ctx);
 
 	line = find_list_mark_anchor(ctx, box);
 	if (line)
@@ -1078,13 +1071,14 @@ static void draw_list_mark(fz_context *ctx, fz_html *box, float page_top, float
 	}
 
 	s = buf;
-	x = box->x - w;
+	trm.e = box->x - w;
+	trm.f = y;
 	while (*s)
 	{
 		s += fz_chartorune(&c, s);
 		g = fz_encode_character(ctx, box->style.font, c);
-		fz_add_text(ctx, text, g, c, x, y);
-		x += fz_advance_glyph(ctx, box->style.font, g) * box->em;
+		fz_add_text(ctx, text, box->style.font, 0, &trm, g, c);
+		trm.e += fz_advance_glyph(ctx, box->style.font, g) * box->em;
 	}
 
 	color[0] = box->style.color.r / 255.0f;
diff --git a/source/pdf/pdf-appearance.c b/source/pdf/pdf-appearance.c
index 9bd3754b..6132f97f 100644
--- a/source/pdf/pdf-appearance.c
+++ b/source/pdf/pdf-appearance.c
@@ -1898,36 +1898,37 @@ void pdf_update_ink_appearance(fz_context *ctx, pdf_document *doc, pdf_annot *an
 	}
 }
 
-static void add_text(fz_context *ctx, font_info *font_rec, fz_text *text, char *str, int str_len, float x, float y)
+static void add_text(fz_context *ctx, font_info *font_rec, fz_text *text, char *str, int str_len, const fz_matrix *tm_)
 {
 	fz_font *font = font_rec->font->font;
-	int mask = FT_LOAD_NO_SCALE | FT_LOAD_IGNORE_TRANSFORM;
+	fz_matrix tm = *tm_;
+	int ucs, gid, n;
 
-	while (str_len--)
+	while (str_len > 0)
 	{
-		FT_Fixed adv;
-
-		/* FIXME: convert str from utf8 to WinAnsi */
-		int gid = FT_Get_Char_Index(font->ft_face, *str);
-		fz_add_text(ctx, text, gid, *str++, x, y);
-
-		FT_Get_Advance(font->ft_face, gid, mask, &adv);
-		x += ((float)adv) * font_rec->da_rec.font_size / ((FT_Face)font->ft_face)->units_per_EM;
+		n = fz_chartorune(&ucs, str);
+		str += n;
+		str_len -= n;
+		gid = fz_encode_character(ctx, font, ucs);
+		fz_add_text(ctx, text, font, 0, &tm, gid, ucs);
+		tm.e += fz_advance_glyph(ctx, font, gid) * font_rec->da_rec.font_size;
 	}
 }
 
 static fz_text *layout_text(fz_context *ctx, font_info *font_rec, char *str, float x, float y)
 {
-	fz_matrix tm;
-	fz_font *font = font_rec->font->font;
 	fz_text *text;
+	fz_matrix tm;
 
 	fz_scale(&tm, font_rec->da_rec.font_size, font_rec->da_rec.font_size);
-	text = fz_new_text(ctx, font, &tm, 0);
+	tm.e = x;
+	tm.f = y;
+
+	text = fz_new_text(ctx);
 
 	fz_try(ctx)
 	{
-		add_text(ctx, font_rec, text, str, strlen(str), x, y);
+		add_text(ctx, font_rec, text, str, strlen(str), &tm);
 	}
 	fz_catch(ctx)
 	{
@@ -1944,6 +1945,7 @@ static fz_text *fit_text(fz_context *ctx, font_info *font_rec, char *str, fz_rec
 	float height = bounds->y1 - bounds->y0;
 	fz_matrix tm;
 	fz_text *text = NULL;
+	fz_text_span *span;
 	text_splitter splitter;
 	float ascender;
 
@@ -1961,14 +1963,15 @@ static fz_text *fit_text(fz_context *ctx, font_info *font_rec, char *str, fz_rec
 			/* Try a layout pass */
 			int line = 0;
 			float font_size;
-			float x = 0.0;
-			float y = 0.0;
 
 			fz_drop_text(ctx, text);
 			text = NULL;
 			font_size = font_rec->da_rec.font_size;
 			fz_scale(&tm, font_size, font_size);
-			text = fz_new_text(ctx, font_rec->font->font, &tm, 0);
+			tm.e = 0;
+			tm.f = 0;
+
+			text = fz_new_text(ctx);
 
 			text_splitter_start_pass(&splitter);
 
@@ -1989,9 +1992,9 @@ static fz_text *fit_text(fz_context *ctx, font_info *font_rec, char *str, fz_rec
 						int wordlen = splitter.text_end-splitter.text_start;
 
 						text_splitter_move(&splitter, -line, &dx, &dy);
-						x += dx;
-						y += dy;
-						add_text(ctx, font_rec, text, word, wordlen, x, y);
+						tm.e += dx;
+						tm.f += dy;
+						add_text(ctx, font_rec, text, word, wordlen, &tm);
 					}
 				}
 
@@ -2004,12 +2007,15 @@ static fz_text *fit_text(fz_context *ctx, font_info *font_rec, char *str, fz_rec
 
 		/* Post process text with the scale determined by the splitter
 		 * and with the required offst */
-		fz_pre_scale(&text->trm, splitter.scale, splitter.scale);
-		ascender = font_rec->font->ascent * font_rec->da_rec.font_size * splitter.scale / 1000.0f;
-		for (i = 0; i < text->len; i++)
+		for (span = text->head; span; span = span->next)
 		{
-			text->items[i].x = text->items[i].x * splitter.scale + bounds->x0;
-			text->items[i].y = text->items[i].y * splitter.scale + bounds->y1 - ascender;
+			fz_pre_scale(&span->trm, splitter.scale, splitter.scale);
+			ascender = font_rec->font->ascent * font_rec->da_rec.font_size * splitter.scale / 1000.0f;
+			for (i = 0; i < span->len; i++)
+			{
+				span->items[i].x = span->items[i].x * splitter.scale + bounds->x0;
+				span->items[i].y = span->items[i].y * splitter.scale + bounds->y1 - ascender;
+			}
 		}
 	}
 	fz_catch(ctx)
diff --git a/source/pdf/pdf-device.c b/source/pdf/pdf-device.c
index 51c5ee9e..a2ee1295 100644
--- a/source/pdf/pdf-device.c
+++ b/source/pdf/pdf-device.c
@@ -667,7 +667,7 @@ pdf_dev_pop(fz_context *ctx, pdf_device *pdev)
 }
 
 static void
-pdf_dev_text(fz_context *ctx, pdf_device *pdev, fz_text *text, float size)
+pdf_dev_text_span(fz_context *ctx, pdf_device *pdev, fz_text_span *span, float size)
 {
 	int mask = FT_LOAD_NO_SCALE | FT_LOAD_IGNORE_TRANSFORM;
 	int i;
@@ -686,9 +686,9 @@ pdf_dev_text(fz_context *ctx, pdf_device *pdev, fz_text *text, float size)
 	fz_invert_matrix(&inverse, &trunc_trm);
 
 	i = 0;
-	while (i < text->len)
+	while (i < span->len)
 	{
-		fz_text_item *it = &text->items[i];
+		fz_text_item *it = &span->items[i];
 		fz_point delta;
 		float x;
 		int j;
@@ -704,17 +704,17 @@ pdf_dev_text(fz_context *ctx, pdf_device *pdev, fz_text *text, float size)
 		}
 
 		j = i+1;
-		if (text->font->ft_face)
+		if (span->font->ft_face)
 		{
 			/* Find prefix of text for which the advance of each character accounts
 			 * for the position offset */
 			x = it->x;
-			while (j < text->len)
+			while (j < span->len)
 			{
 				FT_Fixed adv;
-				FT_Get_Advance(text->font->ft_face, text->items[j-1].gid, mask, &adv);
-				x += (float)adv * size /((FT_Face)text->font->ft_face)->units_per_EM;
-				if (fabs(x - text->items[j].x) > ALLOWED_TEXT_POS_ERROR || fabs(it->y - text->items[j].y) > ALLOWED_TEXT_POS_ERROR)
+				FT_Get_Advance(span->font->ft_face, span->items[j-1].gid, mask, &adv);
+				x += (float)adv * size /((FT_Face)span->font->ft_face)->units_per_EM;
+				if (fabs(x - span->items[j].x) > ALLOWED_TEXT_POS_ERROR || fabs(it->y - span->items[j].y) > ALLOWED_TEXT_POS_ERROR)
 					break;
 				j++;
 			}
@@ -725,7 +725,7 @@ pdf_dev_text(fz_context *ctx, pdf_device *pdev, fz_text *text, float size)
 		{
 			/* FIXME: should use it->gid, rather than it->ucs, and convert
 			* to the correct encoding */
-			fz_buffer_printf(ctx, gs->buf, "%02x", text->items[i].ucs);
+			fz_buffer_printf(ctx, gs->buf, "%02x", span->items[i].ucs);
 		}
 		fz_buffer_printf(ctx, gs->buf, "> Tj\n");
 	}
@@ -935,83 +935,105 @@ pdf_dev_clip_stroke_path(fz_context *ctx, fz_device *dev, fz_path *path, const f
 
 static void
 pdf_dev_fill_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matrix *ctm,
-	fz_colorspace *colorspace, float *color, float alpha)
+		fz_colorspace *colorspace, float *color, float alpha)
 {
 	pdf_device *pdev = (pdf_device*)dev;
-	fz_matrix trm = text->trm;
-	float size = fz_matrix_expansion(&trm);
+	fz_text_span *span;
 
-	fz_pre_scale(&trm, 1/size, 1/size);
+	for (span = text->head; span; span = span->next)
+	{
+		fz_matrix trm = span->trm;
+		float size = fz_matrix_expansion(&trm);
 
-	pdf_dev_begin_text(ctx, pdev, &trm, 0);
-	pdf_dev_font(ctx, pdev, text->font, size);
-	pdf_dev_ctm(ctx, pdev, ctm);
-	pdf_dev_alpha(ctx, pdev, alpha, 0);
-	pdf_dev_color(ctx, pdev, colorspace, color, 0);
-	pdf_dev_text(ctx, pdev, text, size);
+		fz_pre_scale(&trm, 1/size, 1/size);
+
+		pdf_dev_begin_text(ctx, pdev, &trm, 0);
+		pdf_dev_font(ctx, pdev, span->font, size);
+		pdf_dev_ctm(ctx, pdev, ctm);
+		pdf_dev_alpha(ctx, pdev, alpha, 0);
+		pdf_dev_color(ctx, pdev, colorspace, color, 0);
+		pdf_dev_text_span(ctx, pdev, span, size);
+	}
 }
 
 static void
 pdf_dev_stroke_text(fz_context *ctx, fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm,
-	fz_colorspace *colorspace, float *color, float alpha)
+		fz_colorspace *colorspace, float *color, float alpha)
 {
 	pdf_device *pdev = (pdf_device*)dev;
-	fz_matrix trm = text->trm;
-	float size = fz_matrix_expansion(&trm);
+	fz_text_span *span;
 
-	fz_pre_scale(&trm, 1/size, 1/size);
+	for (span = text->head; span; span = span->next)
+	{
+		fz_matrix trm = span->trm;
+		float size = fz_matrix_expansion(&trm);
 
-	pdf_dev_begin_text(ctx, pdev, &text->trm, 1);
-	pdf_dev_font(ctx, pdev, text->font, 1);
-	pdf_dev_ctm(ctx, pdev, ctm);
-	pdf_dev_alpha(ctx, pdev, alpha, 1);
-	pdf_dev_color(ctx, pdev, colorspace, color, 1);
-	pdf_dev_text(ctx, pdev, text, size);
+		fz_pre_scale(&trm, 1/size, 1/size);
+
+		pdf_dev_begin_text(ctx, pdev, &span->trm, 1);
+		pdf_dev_font(ctx, pdev, span->font, 1);
+		pdf_dev_ctm(ctx, pdev, ctm);
+		pdf_dev_alpha(ctx, pdev, alpha, 1);
+		pdf_dev_color(ctx, pdev, colorspace, color, 1);
+		pdf_dev_text_span(ctx, pdev, span, size);
+	}
 }
 
 static void
 pdf_dev_clip_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matrix *ctm, int accumulate)
 {
 	pdf_device *pdev = (pdf_device*)dev;
-	fz_matrix trm = text->trm;
-	float size = fz_matrix_expansion(&trm);
+	fz_text_span *span;
+	for (span = text->head; span; span = span->next)
+	{
+		fz_matrix trm = span->trm;
+		float size = fz_matrix_expansion(&trm);
 
-	fz_pre_scale(&trm, 1/size, 1/size);
+		fz_pre_scale(&trm, 1/size, 1/size);
 
-	pdf_dev_begin_text(ctx, pdev, &text->trm, 0);
-	pdf_dev_ctm(ctx, pdev, ctm);
-	pdf_dev_font(ctx, pdev, text->font, 7);
-	pdf_dev_text(ctx, pdev, text, size);
+		pdf_dev_begin_text(ctx, pdev, &span->trm, 0);
+		pdf_dev_ctm(ctx, pdev, ctm);
+		pdf_dev_font(ctx, pdev, span->font, 7);
+		pdf_dev_text_span(ctx, pdev, span, size);
+	}
 }
 
 static void
 pdf_dev_clip_stroke_text(fz_context *ctx, fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
 {
 	pdf_device *pdev = (pdf_device*)dev;
-	fz_matrix trm = text->trm;
-	float size = fz_matrix_expansion(&trm);
+	fz_text_span *span;
+	for (span = text->head; span; span = span->next)
+	{
+		fz_matrix trm = span->trm;
+		float size = fz_matrix_expansion(&trm);
 
-	fz_pre_scale(&trm, 1/size, 1/size);
+		fz_pre_scale(&trm, 1/size, 1/size);
 
-	pdf_dev_begin_text(ctx, pdev, &text->trm, 0);
-	pdf_dev_font(ctx, pdev, text->font, 5);
-	pdf_dev_ctm(ctx, pdev, ctm);
-	pdf_dev_text(ctx, pdev, text, size);
+		pdf_dev_begin_text(ctx, pdev, &span->trm, 0);
+		pdf_dev_font(ctx, pdev, span->font, 5);
+		pdf_dev_ctm(ctx, pdev, ctm);
+		pdf_dev_text_span(ctx, pdev, span, size);
+	}
 }
 
 static void
 pdf_dev_ignore_text(fz_context *ctx, fz_device *dev, fz_text *text, const fz_matrix *ctm)
 {
 	pdf_device *pdev = (pdf_device*)dev;
-	fz_matrix trm = text->trm;
-	float size = fz_matrix_expansion(&trm);
+	fz_text_span *span;
+	for (span = text->head; span; span = span->next)
+	{
+		fz_matrix trm = span->trm;
+		float size = fz_matrix_expansion(&trm);
 
-	fz_pre_scale(&trm, 1/size, 1/size);
+		fz_pre_scale(&trm, 1/size, 1/size);
 
-	pdf_dev_begin_text(ctx, pdev, &text->trm, 0);
-	pdf_dev_ctm(ctx, pdev, ctm);
-	pdf_dev_font(ctx, pdev, text->font, 3);
-	pdf_dev_text(ctx, pdev, text, size);
+		pdf_dev_begin_text(ctx, pdev, &span->trm, 0);
+		pdf_dev_ctm(ctx, pdev, ctm);
+		pdf_dev_font(ctx, pdev, span->font, 3);
+		pdf_dev_text_span(ctx, pdev, span, size);
+	}
 }
 
 static void
diff --git a/source/pdf/pdf-op-run.c b/source/pdf/pdf-op-run.c
index dbd9386d..ea2384be 100644
--- a/source/pdf/pdf-op-run.c
+++ b/source/pdf/pdf-op-run.c
@@ -754,7 +754,7 @@ pdf_flush_text(fz_context *ctx, pdf_run_processor *pr)
 			fz_adjust_rect_for_stroke(ctx, &tb, gstate->stroke_state, &gstate->ctm);
 
 		/* Don't bother sending a text group with nothing in it */
-		if (text->len == 0)
+		if (!text->head)
 			break;
 
 		if (dofill || dostroke)
@@ -921,22 +921,12 @@ pdf_show_char(fz_context *ctx, pdf_run_processor *pr, int cid)
 	 * uncachable, then render direct. */
 	render_direct = (!fontdesc->font->ft_face && pr->nested_depth > 0) || !fz_glyph_cacheable(ctx, fontdesc->font, gid);
 
-	/* flush buffered text if face or matrix or rendermode has changed */
-	if (!pr->text ||
-		fontdesc->font != pr->text->font ||
-		fontdesc->wmode != pr->text->wmode ||
-		fabsf(trm.a - pr->text->trm.a) > FLT_EPSILON ||
-		fabsf(trm.b - pr->text->trm.b) > FLT_EPSILON ||
-		fabsf(trm.c - pr->text->trm.c) > FLT_EPSILON ||
-		fabsf(trm.d - pr->text->trm.d) > FLT_EPSILON ||
-		gstate->render != pr->text_mode ||
-		render_direct)
+	/* flush buffered text if rendermode has changed */
+	if (!pr->text || gstate->render != pr->text_mode || render_direct)
 	{
 		gstate = pdf_flush_text(ctx, pr);
 
-		pr->text = fz_new_text(ctx, fontdesc->font, &trm, fontdesc->wmode);
-		pr->text->trm.e = 0;
-		pr->text->trm.f = 0;
+		pr->text = fz_new_text(ctx);
 		pr->text_mode = gstate->render;
 		pr->text_bbox = fz_empty_rect;
 	}
@@ -956,11 +946,11 @@ pdf_show_char(fz_context *ctx, pdf_run_processor *pr, int cid)
 	fz_union_rect(&pr->text_bbox, &bbox);
 
 	/* add glyph to textobject */
-	fz_add_text(ctx, pr->text, gid, ucsbuf[0], trm.e, trm.f);
+	fz_add_text(ctx, pr->text, fontdesc->font, fontdesc->wmode, &trm, gid, ucsbuf[0]);
 
 	/* add filler glyphs for one-to-many unicode mapping */
 	for (i = 1; i < ucslen; i++)
-		fz_add_text(ctx, pr->text, -1, ucsbuf[i], trm.e, trm.f);
+		fz_add_text(ctx, pr->text, fontdesc->font, fontdesc->wmode, &trm, -1, ucsbuf[i]);
 
 	if (fontdesc->wmode == 0)
 	{
@@ -1658,7 +1648,7 @@ static void pdf_run_Tw(fz_context *ctx, pdf_processor *proc, float wordspace)
 static void pdf_run_Tz(fz_context *ctx, pdf_processor *proc, float scale)
 {
 	pdf_run_processor *pr = (pdf_run_processor *)proc;
-	pdf_gstate *gstate = pdf_flush_text(ctx, pr);
+	pdf_gstate *gstate = pr->gstate + pr->gtop;
 	gstate->scale = scale / 100;
 }
 
diff --git a/source/xps/xps-glyphs.c b/source/xps/xps-glyphs.c
index 06d2820d..4434c4ed 100644
--- a/source/xps/xps-glyphs.c
+++ b/source/xps/xps-glyphs.c
@@ -348,7 +348,6 @@ xps_parse_glyphs_imp(fz_context *ctx, xps_document *doc, const fz_matrix *ctm,
 	xps_glyph_metrics mtx;
 	fz_text *text;
 	fz_matrix tm;
-	float e, f;
 	float x = originx;
 	float y = originy;
 	char *us = unicode;
@@ -372,7 +371,7 @@ xps_parse_glyphs_imp(fz_context *ctx, xps_document *doc, const fz_matrix *ctm,
 	else
 		fz_scale(&tm, size, -size);
 
-	text = fz_new_text(ctx, font, &tm, is_sideways);
+	text = fz_new_text(ctx);
 
 	while ((us && un > 0) || (is && *is))
 	{
@@ -440,16 +439,16 @@ xps_parse_glyphs_imp(fz_context *ctx, xps_document *doc, const fz_matrix *ctm,
 
 			if (is_sideways)
 			{
-				e = x + u_offset + (mtx.vorg * size);
-				f = y - v_offset + (mtx.hadv * 0.5f * size);
+				tm.e = x + u_offset + (mtx.vorg * size);
+				tm.f = y - v_offset + (mtx.hadv * 0.5f * size);
 			}
 			else
 			{
-				e = x + u_offset;
-				f = y - v_offset;
+				tm.e = x + u_offset;
+				tm.f = y - v_offset;
 			}
 
-			fz_add_text(ctx, text, glyph_index, char_code, e, f);
+			fz_add_text(ctx, text, font, is_sideways, &tm, glyph_index, char_code);
 
 			x += advance * 0.01f * size;
 		}
-- 
cgit v1.2.3