Пример #1
0
static void
fz_process_mesh_type2(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_mesh_processor *painter)
{
	fz_point p0, p1, dir;
	fz_vertex v0, v1, v2, v3;
	fz_vertex e0, e1;
	float theta;
	float zero = 0;
	float one = 1;

	p0.x = shade->u.l_or_r.coords[0][0];
	p0.y = shade->u.l_or_r.coords[0][1];
	p1.x = shade->u.l_or_r.coords[1][0];
	p1.y = shade->u.l_or_r.coords[1][1];
	dir.x = p0.y - p1.y;
	dir.y = p1.x - p0.x;
	fz_transform_point(&p0, ctm);
	fz_transform_point(&p1, ctm);
	fz_transform_vector(&dir, ctm);
	theta = atan2f(dir.y, dir.x);

	v0.p = fz_point_on_circle(p0, HUGENUM, theta);
	v1.p = fz_point_on_circle(p1, HUGENUM, theta);
	v2.p = fz_point_on_circle(p0, -HUGENUM, theta);
	v3.p = fz_point_on_circle(p1, -HUGENUM, theta);

	fz_prepare_color(ctx, painter, &v0, &zero);
	fz_prepare_color(ctx, painter, &v1, &one);
	fz_prepare_color(ctx, painter, &v2, &zero);
	fz_prepare_color(ctx, painter, &v3, &one);

	paint_quad(ctx, painter, &v0, &v2, &v3, &v1);

	if (shade->u.l_or_r.extend[0])
	{
		e0.p.x = v0.p.x - (p1.x - p0.x) * HUGENUM;
		e0.p.y = v0.p.y - (p1.y - p0.y) * HUGENUM;
		fz_prepare_color(ctx, painter, &e0, &zero);

		e1.p.x = v2.p.x - (p1.x - p0.x) * HUGENUM;
		e1.p.y = v2.p.y - (p1.y - p0.y) * HUGENUM;
		fz_prepare_color(ctx, painter, &e1, &zero);

		paint_quad(ctx, painter, &e0, &v0, &v2, &e1);
	}

	if (shade->u.l_or_r.extend[1])
	{
		e0.p.x = v1.p.x + (p1.x - p0.x) * HUGENUM;
		e0.p.y = v1.p.y + (p1.y - p0.y) * HUGENUM;
		fz_prepare_color(ctx, painter, &e0, &one);

		e1.p.x = v3.p.x + (p1.x - p0.x) * HUGENUM;
		e1.p.y = v3.p.y + (p1.y - p0.y) * HUGENUM;
		fz_prepare_color(ctx, painter, &e1, &one);

		paint_quad(ctx, painter, &e0, &v1, &v3, &e1);
	}
}
Пример #2
0
fz_rect *
fz_text_char_bbox(fz_context *ctx, fz_rect *bbox, fz_text_span *span, int i)
{
	fz_point a, d;
	const fz_point *max;
	fz_text_char *ch;

	if (!span || i >= span->len)
	{
		*bbox = fz_empty_rect;
		return bbox;
	}
	ch = &span->text[i];
	if (i == span->len-1)
		max = &span->max;
	else
		max = &span->text[i+1].p;
	if (span->wmode == 0)
	{
		a.x = 0;
		a.y = span->ascender_max;
		d.x = 0;
		d.y = span->descender_min;
	}
	else
	{
		a.x = span->ascender_max;
		a.y = 0;
		d.x = span->descender_min;
		d.y = 0;
	}
	fz_transform_vector(&a, &span->transform);
	fz_transform_vector(&d, &span->transform);
	bbox->x0 = bbox->x1 = ch->p.x + a.x;
	bbox->y0 = bbox->y1 = ch->p.y + a.y;
	a.x += max->x;
	a.y += max->y;
	add_point_to_rect(bbox, &a);
	a.x = ch->p.x + d.x;
	a.y = ch->p.y + d.y;
	add_point_to_rect(bbox, &a);
	a.x = max->x + d.x;
	a.y = max->y + d.y;
	add_point_to_rect(bbox, &a);
	return bbox;
}
Пример #3
0
static void
add_bbox_to_span(fz_text_span *span)
{
	fz_point a, d;
	fz_rect *bbox = &span->bbox;

	if (!span)
		return;
	if (span->wmode == 0)
	{
		a.x = 0;
		a.y = span->ascender_max;
		d.x = 0;
		d.y = span->descender_min;
	}
	else
	{
		a.x = span->ascender_max;
		a.y = 0;
		d.x = span->descender_min;
		d.y = 0;
	}
	fz_transform_vector(&a, &span->transform);
	fz_transform_vector(&d, &span->transform);
	bbox->x0 = bbox->x1 = span->min.x + a.x;
	bbox->y0 = bbox->y1 = span->min.y + a.y;
	a.x += span->max.x;
	a.y += span->max.y;
	add_point_to_rect(bbox, &a);
	a.x = span->min.x + d.x;
	a.y = span->min.y + d.y;
	add_point_to_rect(bbox, &a);
	a.x = span->max.x + d.x;
	a.y = span->max.y + d.y;
	add_point_to_rect(bbox, &a);
}
/*
	Some explaination of the parameters here is warranted. See:

	http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes

	Add an arc segment to path, that describes a section of an elliptical
	arc from the current point of path to (point_x,point_y), such that:

	The arc segment is taken from an elliptical arc of semi major radius
	size_x, semi minor radius size_y, where the semi major axis of the
	ellipse is rotated by rotation_angle.

	If is_large_arc, then the arc segment is selected to be > 180 degrees.

	If is_clockwise, then the arc sweeps clockwise.
*/
static void
xps_draw_arc(fz_context *doc, fz_path *path,
	float size_x, float size_y, float rotation_angle,
	int is_large_arc, int is_clockwise,
	float point_x, float point_y)
{
	fz_matrix rotmat, revmat;
	fz_matrix mtx;
	fz_point pt;
	float rx, ry;
	float x1, y1, x2, y2;
	float x1t, y1t;
	float cxt, cyt, cx, cy;
	float t1, t2, t3;
	float sign;
	float th1, dth;

	pt = fz_currentpoint(doc, path);
	x1 = pt.x;
	y1 = pt.y;
	x2 = point_x;
	y2 = point_y;
	rx = size_x;
	ry = size_y;

	if (is_clockwise != is_large_arc)
		sign = 1;
	else
		sign = -1;

	fz_rotate(&rotmat, rotation_angle);
	fz_rotate(&revmat, -rotation_angle);

	/* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
	/* Conversion from endpoint to center parameterization */

	/* F.6.6.1 -- ensure radii are positive and non-zero */
	rx = fabsf(rx);
	ry = fabsf(ry);
	if (rx < 0.001f || ry < 0.001f || (x1 == x2 && y1 == y2))
	{
		fz_lineto(doc, path, x2, y2);
		return;
	}

	/* F.6.5.1 */
	pt.x = (x1 - x2) / 2;
	pt.y = (y1 - y2) / 2;
	fz_transform_vector(&pt, &revmat);
	x1t = pt.x;
	y1t = pt.y;

	/* F.6.6.2 -- ensure radii are large enough */
	t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
	if (t1 > 1)
	{
		rx = rx * sqrtf(t1);
		ry = ry * sqrtf(t1);
	}

	/* F.6.5.2 */
	t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
	t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
	t3 = t1 / t2;
	/* guard against rounding errors; sqrt of negative numbers is bad for your health */
	if (t3 < 0) t3 = 0;
	t3 = sqrtf(t3);

	cxt = sign * t3 * (rx * y1t) / ry;
	cyt = sign * t3 * -(ry * x1t) / rx;

	/* F.6.5.3 */
	pt.x = cxt;
	pt.y = cyt;
	fz_transform_vector(&pt, &rotmat);
	cx = pt.x + (x1 + x2) / 2;
	cy = pt.y + (y1 + y2) / 2;

	/* F.6.5.4 */
	{
		fz_point coord1, coord2, coord3, coord4;
		coord1.x = 1;
		coord1.y = 0;
		coord2.x = (x1t - cxt) / rx;
		coord2.y = (y1t - cyt) / ry;
		coord3.x = (x1t - cxt) / rx;
		coord3.y = (y1t - cyt) / ry;
		coord4.x = (-x1t - cxt) / rx;
		coord4.y = (-y1t - cyt) / ry;
		th1 = angle_between(coord1, coord2);
		dth = angle_between(coord3, coord4);
		if (dth < 0 && !is_clockwise)
			dth += (((float)M_PI / 180) * 360);
		if (dth > 0 && is_clockwise)
			dth -= (((float)M_PI / 180) * 360);
	}

	fz_pre_scale(fz_pre_rotate(fz_translate(&mtx, cx, cy), rotation_angle), rx, ry);
	xps_draw_arc_segment(doc, path, &mtx, th1, th1 + dth, is_clockwise);

	fz_lineto(doc, path, point_x, point_y);
}
Пример #5
0
static void
fz_add_text_char_imp(fz_context *ctx, fz_text_device *dev, fz_text_style *style, int c, fz_matrix *trm, float adv, int wmode)
{
	int can_append = 1;
	int add_space = 0;
	fz_point dir, ndir, p, q, r;
	float size;
	fz_point delta;
	float spacing = 0;
	float base_offset = 0;

	if (wmode == 0)
	{
		dir.x = 1;
		dir.y = 0;
	}
	else
	{
		dir.x = 0;
		dir.y = -1;
	}
	fz_transform_vector(&dir, trm);
	ndir = dir;
	fz_normalize_vector(&ndir);
	/* dir = direction vector for motion. ndir = normalised(dir) */

	size = fz_matrix_expansion(trm);

	/* We need to identify where glyphs 'start' (p) and 'stop' (q).
	 * Each glyph holds it's 'start' position, and the next glyph in the
	 * span (or span->max if there is no next glyph) holds it's 'end'
	 * position.
	 *
	 * For both horizontal and vertical motion, trm->{e,f} gives the
	 * bottom left corner of the glyph.
	 *
	 * In horizontal mode:
	 *   + p is bottom left.
	 *   + q is the bottom right
	 * In vertical mode:
	 *   + p is top left (where it advanced from)
	 *   + q is bottom left
	 */
	if (wmode == 0)
	{
		p.x = trm->e;
		p.y = trm->f;
		q.x = trm->e + adv * dir.x;
		q.y = trm->f + adv * dir.y;
	}
	else
	{
		p.x = trm->e - adv * dir.x;
		p.y = trm->f - adv * dir.y;
		q.x = trm->e;
		q.y = trm->f;
	}

	if (dev->cur_span == NULL ||
		trm->a != dev->cur_span->transform.a || trm->b != dev->cur_span->transform.b ||
		trm->c != dev->cur_span->transform.c || trm->d != dev->cur_span->transform.d ||
		dev->cur_span->wmode != wmode)
	{
		/* If the matrix has changed, or the wmode is different (or
		 * if we don't have a span at all), then we can't append. */
#ifdef DEBUG_SPANS
		printf("Transform/WMode changed\n");
#endif
		can_append = 0;
	}
	else
	{
		/* Calculate how far we've moved since the end of the current
		 * span. */
		delta.x = p.x - dev->cur_span->max.x;
		delta.y = p.y - dev->cur_span->max.y;

		/* The transform has not changed, so we know we're in the same
		 * direction. Calculate 2 distances; how far off the previous
		 * baseline we are, together with how far along the baseline
		 * we are from the expected position. */
		spacing = ndir.x * delta.x + ndir.y * delta.y;
		base_offset = -ndir.y * delta.x + ndir.x * delta.y;

		spacing /= size * SPACE_DIST;
		spacing = fabsf(spacing);
		if (fabsf(base_offset) < size * 0.1)
		{
			/* Only a small amount off the baseline - we'll take this */
			if (spacing < 1.0)
			{
				/* Motion is in line, and small. */
			}
			else if (spacing >= 1 && spacing < (SPACE_MAX_DIST/SPACE_DIST))
			{
				/* Motion is in line, but large enough
				 * to warrant us adding a space */
				if (dev->lastchar != ' ' && wmode == 0)
					add_space = 1;
			}
			else
			{
				/* Motion is in line, but too large - split to a new span */
				can_append = 0;
			}
		}
		else
		{
			can_append = 0;
#ifdef DEBUG_SPANS
			spacing = 0;
#endif
		}
	}

#ifdef DEBUG_SPANS
	printf("%c%c append=%d space=%d size=%g spacing=%g base_offset=%g\n", dev->lastchar, c, can_append, add_space, size, spacing, base_offset);
#endif

	if (can_append == 0)
	{
		/* Start a new span */
		add_span_to_soup(ctx, dev->spans, dev->cur_span);
		dev->cur_span = NULL;
		dev->cur_span = fz_new_text_span(ctx, &p, wmode, trm);
		dev->cur_span->spacing = 0;
	}
	if (add_space)
	{
		r.x = - 0.2f;
		r.y = 0;
		fz_transform_point(&r, trm);
		add_char_to_span(ctx, dev->cur_span, ' ', &p, &r, style);
	}
	add_char_to_span(ctx, dev->cur_span, c, &p, &q, style);
}
Пример #6
0
static void
fz_text_extract_span(fz_context *ctx, fz_text_span **last, fz_text *text, fz_matrix ctm, fz_point *pen)
{
	fz_font *font = text->font;
	FT_Face face = font->ft_face;
	fz_matrix tm = text->trm;
	fz_matrix trm;
	float size;
	float adv;
	fz_rect rect;
	fz_point dir, ndir;
	fz_point delta, ndelta;
	float dist, dot;
	float ascender = 1;
	float descender = 0;
	int multi;
	int i, err;

	if (text->len == 0)
		return;

	fz_lock(ctx, FZ_LOCK_FREETYPE);
	if (font->ft_face)
	{
		err = FT_Set_Char_Size(font->ft_face, 64, 64, 72, 72);
		if (err)
			fz_warn(ctx, "freetype set character size: %s", ft_error_string(err));
		ascender = (float)face->ascender / face->units_per_EM;
		descender = (float)face->descender / face->units_per_EM;
	}

	rect = fz_empty_rect;

	if (text->wmode == 0)
	{
		dir.x = 1;
		dir.y = 0;
	}
	else
	{
		dir.x = 0;
		dir.y = 1;
	}

	tm.e = 0;
	tm.f = 0;
	trm = fz_concat(tm, ctm);
	dir = fz_transform_vector(trm, dir);
	dist = sqrtf(dir.x * dir.x + dir.y * dir.y);
	ndir.x = dir.x / dist;
	ndir.y = dir.y / dist;

	size = fz_matrix_expansion(trm);

	multi = 1;

	for (i = 0; i < text->len; i++)
	{
		if (text->items[i].gid < 0)
		{
			fz_add_text_char(ctx, last, font, size, text->wmode, text->items[i].ucs, fz_round_rect(rect));
			multi ++;
			fz_divide_text_chars(last, multi, fz_round_rect(rect));
			continue;
		}
		multi = 1;

		/* Calculate new pen location and delta */
		tm.e = text->items[i].x;
		tm.f = text->items[i].y;
		trm = fz_concat(tm, ctm);

		delta.x = pen->x - trm.e;
		delta.y = pen->y - trm.f;
		if (pen->x == -1 && pen->y == -1)
			delta.x = delta.y = 0;

		dist = sqrtf(delta.x * delta.x + delta.y * delta.y);

		/* Add space and newlines based on pen movement */
		if (dist > 0)
		{
			ndelta.x = delta.x / dist;
			ndelta.y = delta.y / dist;
			dot = ndelta.x * ndir.x + ndelta.y * ndir.y;

			if (dist > size * LINE_DIST)
			{
				fz_add_text_newline(ctx, last, font, size, text->wmode);
			}
			else if (fabsf(dot) > 0.95f && dist > size * SPACE_DIST)
			{
				if ((*last)->len > 0 && (*last)->text[(*last)->len - 1].c != ' ')
				{
					fz_rect spacerect;
					spacerect.x0 = -0.2f;
					spacerect.y0 = 0;
					spacerect.x1 = 0;
					spacerect.y1 = 1;
					spacerect = fz_transform_rect(trm, spacerect);
					fz_add_text_char(ctx, last, font, size, text->wmode, ' ', fz_round_rect(spacerect));
				}
			}
		}

		/* Calculate bounding box and new pen position based on font metrics */
		if (font->ft_face)
		{
			FT_Fixed ftadv = 0;
			int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM;

			/* TODO: freetype returns broken vertical metrics */
			/* if (text->wmode) mask |= FT_LOAD_VERTICAL_LAYOUT; */

			FT_Get_Advance(font->ft_face, text->items[i].gid, mask, &ftadv);
			adv = ftadv / 65536.0f;

			rect.x0 = 0;
			rect.y0 = descender;
			rect.x1 = adv;
			rect.y1 = ascender;
		}
		else
		{
			adv = font->t3widths[text->items[i].gid];
			rect.x0 = 0;
			rect.y0 = descender;
			rect.x1 = adv;
			rect.y1 = ascender;
		}

		rect = fz_transform_rect(trm, rect);
		pen->x = trm.e + dir.x * adv;
		pen->y = trm.f + dir.y * adv;

		fz_add_text_char(ctx, last, font, size, text->wmode, text->items[i].ucs, fz_round_rect(rect));
	}
	fz_unlock(ctx, FZ_LOCK_FREETYPE);
}
Пример #7
0
static void
fz_text_extract_span(fz_text_span **last, fz_text *text, fz_matrix ctm, fz_point *pen)
{
//	fz_font *font = text->font;
//	FT_Face face = font->ft_face;
	fz_matrix tm = text->trm;
	fz_matrix trm;
	float size;
	float adv = 0;
	fz_rect rect;
	fz_point dir, ndir;
	fz_point delta, ndelta;
	float dist, dot;
	float ascender = 1;
	float descender = 0;
	int multi;
	int i;

	if (text->len == 0)
		return;

	rect = fz_empty_rect;

	/* SumatraPDF: TODO: make this depend on the per-glyph displacement vector */
	if (text->wmode == 0)
	{
		dir.x = 1;
		dir.y = 0;
	}
	else
	{
		dir.x = 0;
		dir.y = 1;
	}

	tm.e = 0;
	tm.f = 0;
	trm = fz_concat(tm, ctm);
	dir = fz_transform_vector(trm, dir);
	dist = sqrtf(dir.x * dir.x + dir.y * dir.y);
	ndir.x = dir.x / dist;
	ndir.y = dir.y / dist;

	size = fz_matrix_expansion(trm);

	multi = 1;

	for (i = 0; i < text->len; i++)
	{
		if (text->items[i].gid < 0)
		{
			fz_add_text_char(last, size, text->wmode, text->items[i].ucs, fz_round_rect(rect));
			multi ++;
			fz_divide_text_chars(last, multi, fz_round_rect(rect));
			continue;
		}
		multi = 1;

		/* Calculate new pen location and delta */
		tm.e = text->items[i].x;
		tm.f = text->items[i].y;
		trm = fz_concat(tm, ctm);

		delta.x = pen->x - trm.e;
		delta.y = pen->y - trm.f;
		if (pen->x == -1 && pen->y == -1)
			delta.x = delta.y = 0;

		dist = sqrtf(delta.x * delta.x + delta.y * delta.y);

		/* Add space and newlines based on pen movement */
		if (dist > 0)
		{
			ndelta.x = delta.x / dist;
			ndelta.y = delta.y / dist;
			dot = ndelta.x * ndir.x + ndelta.y * ndir.y;

			if (dist > size * LINE_DIST)
			{
				fz_add_text_newline(last, size, text->wmode);
			}
			else if (fabsf(dot) > 0.95f && dist > size * SPACE_DIST)
			{
				if ((*last)->len > 0 && (*last)->text[(*last)->len - 1].c != ' ')
				{
					fz_rect spacerect;
					spacerect.x0 = -0.2f;
					spacerect.y0 = 0;
					spacerect.x1 = 0;
					spacerect.y1 = 1;
					spacerect = fz_transform_rect(trm, spacerect);
					fz_add_text_char(last, size, text->wmode, ' ', fz_round_rect(spacerect));
				}
			}
		}

		/* Calculate bounding box and new pen position based on font metrics */
		//if (font->ft_face)
		//{
		//	FT_Fixed ftadv = 0;
		//	int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM;

		//	/* TODO: freetype returns broken vertical metrics */
		//	/* if (text->wmode) mask |= FT_LOAD_VERTICAL_LAYOUT; */

		//	FT_Get_Advance(font->ft_face, text->items[i].gid, mask, &ftadv);
		//	adv = ftadv / 65536.0f;

		//	rect.x0 = 0;
		//	rect.y0 = descender;
		//	rect.x1 = adv;
		//	rect.y1 = ascender;
		//}
		//else
		{
//			adv = font->t3widths[text->items[i].gid];
			rect.x0 = 0;
			rect.y0 = descender;
			rect.x1 = adv;
			rect.y1 = ascender;
		}

		rect = fz_transform_rect(trm, rect);
		pen->x = trm.e + dir.x * adv;
		pen->y = trm.f + dir.y * adv;

		fz_add_text_char(last, size, text->wmode, text->items[i].ucs, fz_round_rect(rect));
	}
}
Пример #8
0
static void
fz_text_extract(fz_context *ctx, fz_text_device *dev, fz_text *text, const fz_matrix *ctm, fz_text_style *style)
{
	fz_point *pen = &dev->point;
	fz_font *font = text->font;
	FT_Face face = font->ft_face;
	fz_matrix tm = text->trm;
	fz_matrix trm;
	float size;
	float adv;
	fz_rect rect;
	fz_point dir, ndir;
	fz_point delta, ndelta;
	float dist, dot;
	float ascender = 1;
	float descender = 0;
	int multi;
	int i, j, err;

	if (text->len == 0)
		return;

	if (font->ft_face)
	{
		fz_lock(ctx, FZ_LOCK_FREETYPE);
		err = FT_Set_Char_Size(font->ft_face, 64, 64, 72, 72);
		if (err)
			fz_warn(ctx, "freetype set character size: %s", ft_error_string(err));
		ascender = (float)face->ascender / face->units_per_EM;
		descender = (float)face->descender / face->units_per_EM;
		fz_unlock(ctx, FZ_LOCK_FREETYPE);
	}
	else if (font->t3procs && !fz_is_empty_rect(&font->bbox))
	{
		ascender = font->bbox.y1;
		descender = font->bbox.y0;
	}

	rect = fz_empty_rect;

	/* SumatraPDF: TODO: make this depend on the per-glyph displacement vector */
	if (text->wmode == 0)
	{
		dir.x = 1;
		dir.y = 0;
	}
	else
	{
		dir.x = 0;
		dir.y = 1;
	}

	tm.e = 0;
	tm.f = 0;
	fz_concat(&trm, &tm, ctm);

	fz_transform_vector(&dir, &trm);
	dist = sqrtf(dir.x * dir.x + dir.y * dir.y);
	ndir.x = dir.x / dist;
	ndir.y = dir.y / dist;

	size = fz_matrix_expansion(&trm);

	for (i = 0; i < text->len; i++)
	{
		/* Calculate new pen location and delta */
		tm.e = text->items[i].x;
		tm.f = text->items[i].y;
		fz_concat(&trm, &tm, ctm);

		delta.x = pen->x - trm.e;
		delta.y = pen->y - trm.f;
		if (pen->x == -1 && pen->y == -1)
			delta.x = delta.y = 0;

		dist = sqrtf(delta.x * delta.x + delta.y * delta.y);

		/* Add space and newlines based on pen movement */
		if (dist > 0)
		{
			/* SumatraPDF: don't add spaces for large overlapping glyphs */
			fz_text_span *last = &dev->cur_span;
			if (last->len == 0 && dev->cur_line.len > 0)
				last = &dev->cur_line.spans[dev->cur_line.len - 1];

			ndelta.x = delta.x / dist;
			ndelta.y = delta.y / dist;
			dot = ndelta.x * ndir.x + ndelta.y * ndir.y;

			/* SumatraPDF: don't merge multiple lines into one */
			if (dist > size * LINE_DIST && hypotf(delta.y * ndir.x, delta.x * ndir.y) > size * 0.5f)
			{
				fz_flush_text_line(ctx, dev, style);
				dev->lastchar = ' ';
			}
			else
			/* SumatraPDF: use 0.95f instead of 0.9995f for slightly better results */
			if (fabsf(dot) > 0.95f && dist > size * SPACE_DIST && dist < size * SPACE_MAX_DIST)
			{
				if (dev->lastchar != ' ' &&
				/* SumatraPDF: don't add spaces before spaces or for large overlapping glyphs */
					text->items[i].ucs != ' ' && !fz_maps_into_rect(trm, last->text[last->len - 1].bbox))
				{
					fz_rect spacerect;
					spacerect.x0 = -0.2f;
					spacerect.y0 = descender;
					spacerect.x1 = 0;
					spacerect.y1 = ascender;
					fz_transform_rect(&spacerect, &trm);
					fz_add_text_char(ctx, dev, style, ' ', spacerect);
					dev->lastchar = ' ';
				}
			}
			else if (dist > size * LINE_DIST)
			{
				fz_flush_text_line(ctx, dev, style);
				dev->lastchar = ' ';
			}
		}

		/* Calculate bounding box and new pen position based on font metrics */
		if (font->ft_face)
		{
			FT_Fixed ftadv = 0;
			int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM;

			/* TODO: freetype returns broken vertical metrics */
			/* if (text->wmode) mask |= FT_LOAD_VERTICAL_LAYOUT; */

			fz_lock(ctx, FZ_LOCK_FREETYPE);
			err = FT_Set_Char_Size(font->ft_face, 64, 64, 72, 72);
			if (err)
				fz_warn(ctx, "freetype set character size: %s", ft_error_string(err));
			FT_Get_Advance(font->ft_face, text->items[i].gid, mask, &ftadv);
			adv = ftadv / 65536.0f;
			fz_unlock(ctx, FZ_LOCK_FREETYPE);

			rect.x0 = 0;
			rect.y0 = descender;
			rect.x1 = adv;
			rect.y1 = ascender;
		}
		/* SumatraPDF: TODO: this check might no longer be needed */
		else if (text->items[i].gid < 256)
		{
			adv = font->t3widths[text->items[i].gid];
			rect.x0 = 0;
			rect.y0 = descender;
			rect.x1 = adv;
			rect.y1 = ascender;
		}

		fz_transform_rect(&rect, &trm);
		/* cf. http://code.google.com/p/sumatrapdf/issues/detail?id=1839 */
		if (font->ft_face)
		{
			fz_rect bbox;
			fz_bound_glyph(ctx, font, text->items[i].gid, &trm, &bbox);
			rect.y0 = fz_min(rect.y0, bbox.y0);
			rect.y1 = fz_max(rect.y1, bbox.y1);
		}
		pen->x = trm.e + dir.x * adv;
		pen->y = trm.f + dir.y * adv;

		/* Check for one glyph to many char mapping */
		for (j = i + 1; j < text->len; j++)
			if (text->items[j].gid >= 0)
				break;
		multi = j - i;

		if (multi == 1)
		{
			fz_add_text_char(ctx, dev, style, text->items[i].ucs, rect);
		}
		else
		{
			for (j = 0; j < multi; j++)
			{
				fz_rect part = fz_split_bbox(rect, j, multi);
				fz_add_text_char(ctx, dev, style, text->items[i + j].ucs, part);
			}
			i += j - 1;
		}

		dev->lastchar = text->items[i].ucs;
	}
}