Esempio n. 1
0
static void
fz_drawfillpath(void *user, fz_path *path, int evenodd, fz_matrix ctm,
	fz_colorspace *colorspace, float *color, float alpha)
{
	fz_drawdevice *dev = user;
	fz_colorspace *model = dev->dest->colorspace;
	float expansion = fz_matrixexpansion(ctm);
	float flatness = 0.3f / expansion;
	unsigned char colorbv[FZ_MAXCOLORS + 1];
	float colorfv[FZ_MAXCOLORS];
	fz_bbox bbox;
	int i;

	fz_resetgel(dev->gel, dev->scissor);
	fz_fillpath(dev->gel, path, ctm, flatness);
	fz_sortgel(dev->gel);

	bbox = fz_boundgel(dev->gel);
	bbox = fz_intersectbbox(bbox, dev->scissor);

	if (fz_isemptyrect(bbox))
		return;

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

	fz_scanconvert(dev->gel, dev->ael, evenodd, bbox, dev->dest, colorbv);
}
Esempio n. 2
0
static fz_error *
renderpath(fz_renderer *gc, fz_pathnode *path, fz_matrix ctm)
{
	fz_error *error;
	float flatness;
	fz_irect gbox;
	fz_irect clip;

	flatness = 0.3 / fz_matrixexpansion(ctm);
	if (flatness < 0.1)
		flatness = 0.1;

	fz_resetgel(gc->gel, HS, VS);

	if (path->paint == FZ_STROKE)
	{
		if (path->dash)
			error = fz_dashpath(gc->gel, path, ctm, flatness);
		else
			error = fz_strokepath(gc->gel, path, ctm, flatness);
	}
	else
		error = fz_fillpath(gc->gel, path, ctm, flatness);
	if (error)
		return error;

	fz_sortgel(gc->gel);

	gbox = fz_boundgel(gc->gel);
	clip = fz_intersectirects(gc->clip, gbox);

	if (fz_isemptyrect(clip))
		return nil;

DEBUG("path %s;\n", path->paint == FZ_STROKE ? "stroke" : "fill");

	if (gc->flag & FRGB)
	{
		return fz_scanconvert(gc->gel, gc->ael, path->paint == FZ_EOFILL,
					clip, gc->over, gc->rgb, 1);
	}
	else if (gc->flag & FOVER)
	{
		return fz_scanconvert(gc->gel, gc->ael, path->paint == FZ_EOFILL,
					clip, gc->over, nil, 1);
	}
	else
	{
		error = fz_newpixmapwithrect(&gc->dest, clip, 1);
		if (error)
			return error;
		fz_clearpixmap(gc->dest);
		return fz_scanconvert(gc->gel, gc->ael, path->paint == FZ_EOFILL,
					clip, gc->dest, nil, 0);
	}
}
Esempio n. 3
0
static void
fz_drawclippath(void *user, fz_path *path, int evenodd, fz_matrix ctm)
{
	fz_drawdevice *dev = user;
	fz_colorspace *model = dev->dest->colorspace;
	float expansion = fz_matrixexpansion(ctm);
	float flatness = 0.3f / expansion;
	fz_pixmap *mask, *dest;
	fz_bbox bbox;

	if (dev->top == STACKSIZE)
	{
		fz_warn("assert: too many buffers on stack");
		return;
	}

	fz_resetgel(dev->gel, dev->scissor);
	fz_fillpath(dev->gel, path, ctm, flatness);
	fz_sortgel(dev->gel);

	bbox = fz_boundgel(dev->gel);
	bbox = fz_intersectbbox(bbox, dev->scissor);

	if (fz_isemptyrect(bbox) || fz_isrectgel(dev->gel))
	{
		dev->stack[dev->top].scissor = dev->scissor;
		dev->stack[dev->top].mask = nil;
		dev->stack[dev->top].dest = nil;
		dev->scissor = bbox;
		dev->top++;
		return;
	}

	mask = fz_newpixmapwithrect(nil, bbox);
	dest = fz_newpixmapwithrect(model, bbox);

	fz_clearpixmap(mask, 0);
	fz_clearpixmap(dest, 0);

	fz_scanconvert(dev->gel, dev->ael, evenodd, bbox, mask, nil);

	dev->stack[dev->top].scissor = dev->scissor;
	dev->stack[dev->top].mask = mask;
	dev->stack[dev->top].dest = dev->dest;
	dev->scissor = bbox;
	dev->dest = dest;
	dev->top++;
}
Esempio n. 4
0
static void
fz_drawclipstrokepath(void *user, fz_path *path, fz_strokestate *stroke, fz_matrix ctm)
{
	fz_drawdevice *dev = user;
	fz_colorspace *model = dev->dest->colorspace;
	float expansion = fz_matrixexpansion(ctm);
	float flatness = 0.3f / expansion;
	float linewidth = stroke->linewidth;
	fz_pixmap *mask, *dest;
	fz_bbox bbox;

	if (dev->top == STACKSIZE)
	{
		fz_warn("assert: too many buffers on stack");
		return;
	}

	if (linewidth * expansion < 0.1f)
		linewidth = 1 / expansion;

	fz_resetgel(dev->gel, dev->scissor);
	if (stroke->dashlen > 0)
		fz_dashpath(dev->gel, path, stroke, ctm, flatness, linewidth);
	else
		fz_strokepath(dev->gel, path, stroke, ctm, flatness, linewidth);
	fz_sortgel(dev->gel);

	bbox = fz_boundgel(dev->gel);
	bbox = fz_intersectbbox(bbox, dev->scissor);

	mask = fz_newpixmapwithrect(nil, bbox);
	dest = fz_newpixmapwithrect(model, bbox);

	fz_clearpixmap(mask, 0);
	fz_clearpixmap(dest, 0);

	if (!fz_isemptyrect(bbox))
		fz_scanconvert(dev->gel, dev->ael, 0, bbox, mask, nil);

	dev->stack[dev->top].scissor = dev->scissor;
	dev->stack[dev->top].mask = mask;
	dev->stack[dev->top].dest = dev->dest;
	dev->scissor = bbox;
	dev->dest = dest;
	dev->top++;
}
Esempio n. 5
0
static void
fz_drawstrokepath(void *user, fz_path *path, fz_strokestate *stroke, fz_matrix ctm,
	fz_colorspace *colorspace, float *color, float alpha)
{
	fz_drawdevice *dev = user;
	fz_colorspace *model = dev->dest->colorspace;
	float expansion = fz_matrixexpansion(ctm);
	float flatness = 0.3f / expansion;
	float linewidth = stroke->linewidth;
	unsigned char colorbv[FZ_MAXCOLORS + 1];
	float colorfv[FZ_MAXCOLORS];
	fz_bbox bbox;
	int i;

	if (linewidth * expansion < 0.1f)
		linewidth = 1 / expansion;

	fz_resetgel(dev->gel, dev->scissor);
	if (stroke->dashlen > 0)
		fz_dashpath(dev->gel, path, stroke, ctm, flatness, linewidth);
	else
		fz_strokepath(dev->gel, path, stroke, ctm, flatness, linewidth);
	fz_sortgel(dev->gel);

	bbox = fz_boundgel(dev->gel);
	bbox = fz_intersectbbox(bbox, dev->scissor);

	if (fz_isemptyrect(bbox))
		return;

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

	fz_scanconvert(dev->gel, dev->ael, 0, bbox, dev->dest, colorbv);
}
Esempio n. 6
0
fz_error
fz_renderftglyph(fz_glyph *glyph, fz_font *font, int gid, fz_matrix trm)
{
	FT_Face face = font->ftface;
	FT_Matrix m;
	FT_Vector v;
	FT_Error fterr;
	int x, y;

#if 0
	/* We lost this feature in refactoring.
	 * We can't access pdf_fontdesc metrics from fz_font.
	 * The pdf_fontdesc metrics are character based (cid),
	 * where the glyph being rendered is given by glyph (gid).
	 */
	if (font->ftsubstitute && font->wmode == 0)
	{
		fz_hmtx subw;
		int realw;
		float scale;

		fterr = FT_Set_Char_Size(face, 1000, 1000, 72, 72);
		if (fterr)
			return fz_warn("freetype setting character size: %s", ft_errorstring(fterr));

		fterr = FT_Load_Glyph(font->ftface, gid,
			FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM);
		if (fterr)
			return fz_throw("freetype failed to load glyph: %s", ft_errorstring(fterr));

		realw = ((FT_Face)font->ftface)->glyph->advance.x;
		subw = fz_gethmtx(font, cid); // <-- this is the offender
		if (realw)
			scale = (float) subw.w / realw;
		else
			scale = 1.0;

		trm = fz_concat(fz_scale(scale, 1.0), trm);
	}
#endif

	glyph->w = 0;
	glyph->h = 0;
	glyph->x = 0;
	glyph->y = 0;
	glyph->samples = nil;

	/* freetype mutilates complex glyphs if they are loaded
	 * with FT_Set_Char_Size 1.0. it rounds the coordinates
	 * before applying transformation. to get more precision in
	 * freetype, we shift part of the scale in the matrix
	 * into FT_Set_Char_Size instead
	 */

	m.xx = trm.a * 64; /* should be 65536 */
	m.yx = trm.b * 64;
	m.xy = trm.c * 64;
	m.yy = trm.d * 64;
	v.x = trm.e * 64;
	v.y = trm.f * 64;

	fterr = FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */
	if (fterr)
		fz_warn("freetype setting character size: %s", ft_errorstring(fterr));
	FT_Set_Transform(face, &m, &v);

	if (font->fthint)
	{
		/* Enable hinting, but keep the huge char size so that
		 * it is hinted for a character. This will in effect nullify
		 * the effect of grid fitting. This form of hinting should
		 * only be used for DynaLab and similar tricky TrueType fonts,
		 * so that we get the correct outline shape.
		 */
#ifdef USE_HINTING
		/* If you really want grid fitting, enable this code. */
		float scale = fz_matrixexpansion(trm);
		m.xx = trm.a * 65536 / scale;
		m.xy = trm.b * 65536 / scale;
		m.yx = trm.c * 65536 / scale;
		m.yy = trm.d * 65536 / scale;
		v.x = 0;
		v.y = 0;

		fterr = FT_Set_Char_Size(face, 64 * scale, 64 * scale, 72, 72);
		if (fterr)
			fz_warn("freetype setting character size: %s", ft_errorstring(fterr));
		FT_Set_Transform(face, &m, &v);
#endif
		fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP);
		if (fterr)
			fz_warn("freetype load glyph (gid %d): %s", gid, ft_errorstring(fterr));
	}
	else
	{
		fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING);
		if (fterr)
			fz_warn("freetype load glyph (gid %d): %s", gid, ft_errorstring(fterr));
	}

	fterr = FT_Render_Glyph(face->glyph, ft_render_mode_normal);
	if (fterr)
		fz_warn("freetype render glyph (gid %d): %s", gid, ft_errorstring(fterr));

	glyph->w = face->glyph->bitmap.width;
	glyph->h = face->glyph->bitmap.rows;
	glyph->x = face->glyph->bitmap_left;
	glyph->y = face->glyph->bitmap_top - glyph->h;
	glyph->samples = face->glyph->bitmap.buffer;

	for (y = 0; y < glyph->h / 2; y++)
	{
		for (x = 0; x < glyph->w; x++)
		{
			unsigned char a = glyph->samples[y * glyph->w + x ];
			unsigned char b = glyph->samples[(glyph->h - y - 1) * glyph->w + x];
			glyph->samples[y * glyph->w + x ] = b;
			glyph->samples[(glyph->h - y - 1) * glyph->w + x] = a;
		}
	}

	return fz_okay;
}
Esempio n. 7
0
static fz_error *
renderpath(fz_renderer *gc, fz_pathnode *path, fz_matrix ctm)
{
	fz_error *error;
	float flatness;
	fz_irect gbox;
	fz_irect clip;
	float expansion = fz_matrixexpansion(ctm);

	flatness = 0.3 / expansion;
	if (flatness < 0.1)
		flatness = 0.1;

	fz_resetgel(gc->gel, gc->clip);

	if (path->paint == FZ_STROKE)
	{
		float lw = path->linewidth;
		/* Check for hairline */
		if (lw * expansion < 0.1) {
			lw = 1.0f / expansion;
		}
		if (path->dash)
			error = fz_dashpath(gc->gel, path, ctm, flatness, lw);
		else
			error = fz_strokepath(gc->gel, path, ctm, flatness, lw);
	}
	else
		error = fz_fillpath(gc->gel, path, ctm, flatness);
	if (error)
		return error;

	fz_sortgel(gc->gel);

	gbox = fz_boundgel(gc->gel);
	clip = fz_intersectirects(gc->clip, gbox);

	if (fz_isemptyrect(clip))
		return fz_okay;

DEBUG("path %s;\n", path->paint == FZ_STROKE ? "stroke" : "fill");

	if (gc->flag & FRGB)
	{
DEBUG(" path rgb %d %d %d %d, %d %d %d\n", gc->argb[0], gc->argb[1], gc->argb[2], gc->argb[3], gc->argb[4], gc->argb[5], gc->argb[6]);
		return fz_scanconvert(gc->gel, gc->ael, path->paint == FZ_EOFILL,
					clip, gc->over, gc->argb, 1);
	}
	else if (gc->flag & FOVER)
	{
		return fz_scanconvert(gc->gel, gc->ael, path->paint == FZ_EOFILL,
					clip, gc->over, nil, 1);
	}
	else
	{
		error = fz_newpixmapwithrect(&gc->dest, clip, 1);
		if (error)
			return error;
		fz_clearpixmap(gc->dest);
		return fz_scanconvert(gc->gel, gc->ael, path->paint == FZ_EOFILL,
					clip, gc->dest, nil, 0);
	}
}
Esempio n. 8
0
static fz_error *
ftrender(fz_glyph *glyph, fz_font *fzfont, int cid, fz_matrix trm)
{
	pdf_font *font = (pdf_font*)fzfont;
	FT_Face face = font->ftface;
	FT_Matrix m;
	FT_Vector v;
	FT_Error fterr;
	float scale;
	int gid;
	int x, y;
	int hint = font->hint;

	gid = ftcidtogid(font, cid);

	if (font->substitute && fzfont->wmode == 0)
	{
		fz_hmtx subw;
		int realw;

		FT_Set_Char_Size(face, 1000, 1000, 72, 72);

		fterr = FT_Load_Glyph(font->ftface, gid,
				FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM);
		if (fterr)
			return fz_throw("freetype failed to load glyph: %s", ft_errstr(fterr));

		realw = ((FT_Face)font->ftface)->glyph->advance.x;
		subw = fz_gethmtx(fzfont, cid);
		if (realw)
			scale = (float) subw.w / realw;
		else
			scale = 1.0;

		trm = fz_concat(fz_scale(scale, 1.0), trm);
	}

	glyph->w = 0;
	glyph->h = 0;
	glyph->x = 0;
	glyph->y = 0;
	glyph->samples = nil;

	/* freetype mutilates complex glyphs if they are loaded
	 * with FT_Set_Char_Size 1.0. it rounds the coordinates
	 * before applying transformation. to get more precision in
	 * freetype, we shift part of the scale in the matrix
	 * into FT_Set_Char_Size instead
	 */

#ifdef HINT
	hint = 1;
#endif

	if (hint)
	{
		scale = fz_matrixexpansion(trm);
		m.xx = trm.a * 65536 / scale;
		m.yx = trm.b * 65536 / scale;
		m.xy = trm.c * 65536 / scale;
		m.yy = trm.d * 65536 / scale;
		v.x = 0;
		v.y = 0;

		FT_Set_Char_Size(face, 64 * scale, 64 * scale, 72, 72);
		FT_Set_Transform(face, &m, &v);

		fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP);
		if (fterr)
			fz_warn("freetype load glyph: %s", ft_errstr(fterr));
	}
	else
	{
		m.xx = trm.a * 64;	/* should be 65536 */
		m.yx = trm.b * 64;
		m.xy = trm.c * 64;
		m.yy = trm.d * 64;
		v.x = trm.e * 64;
		v.y = trm.f * 64;

		FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */
		FT_Set_Transform(face, &m, &v);

		fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING);
		if (fterr)
			fz_warn("freetype load glyph: %s", ft_errstr(fterr));
	}

	fterr = FT_Render_Glyph(face->glyph, ft_render_mode_normal);
	if (fterr)
		fz_warn("freetype render glyph: %s", ft_errstr(fterr));

	glyph->w = face->glyph->bitmap.width;
	glyph->h = face->glyph->bitmap.rows;
	glyph->x = face->glyph->bitmap_left;
	glyph->y = face->glyph->bitmap_top - glyph->h;
	glyph->samples = face->glyph->bitmap.buffer;

	for (y = 0; y < glyph->h / 2; y++)
	{
		for (x = 0; x < glyph->w; x++)
		{
			unsigned char a = glyph->samples[y * glyph->w + x ];
			unsigned char b = glyph->samples[(glyph->h - y - 1) * glyph->w + x];
			glyph->samples[y * glyph->w + x ] = b;
			glyph->samples[(glyph->h - y - 1) * glyph->w + x] = a;
		}
	}

	return fz_okay;
}
fz_pixmap *
fz_renderftstrokedglyph(fz_font *font, int gid, fz_matrix trm, fz_matrix ctm, fz_strokestate *state)
{
	FT_Face face = font->ftface;
	float expansion = fz_matrixexpansion(ctm);
	int linewidth = state->linewidth * expansion * 64 / 2;
	FT_Matrix m;
	FT_Vector v;
	FT_Error fterr;
	FT_Stroker stroker;
	FT_Glyph glyph;
	FT_BitmapGlyph bitmap;
	fz_pixmap *pix;
	int y;

	m.xx = trm.a * 64; /* should be 65536 */
	m.yx = trm.b * 64;
	m.xy = trm.c * 64;
	m.yy = trm.d * 64;
	v.x = trm.e * 64;
	v.y = trm.f * 64;

	fterr = FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */
	if (fterr)
	{
		fz_warn("FT_Set_Char_Size: %s", ft_errorstring(fterr));
		return nil;
	}

	FT_Set_Transform(face, &m, &v);

	fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING);
	if (fterr)
	{
		fz_warn("FT_Load_Glyph(gid %d): %s", gid, ft_errorstring(fterr));
		return nil;
	}

	fterr = FT_Stroker_New(fz_ftlib, &stroker);
	if (fterr)
	{
		fz_warn("FT_Stroker_New: %s", ft_errorstring(fterr));
		return nil;
	}

	FT_Stroker_Set(stroker, linewidth, state->linecap, state->linejoin, state->miterlimit * 65536);

	fterr = FT_Get_Glyph(face->glyph, &glyph);
	if (fterr)
	{
		fz_warn("FT_Get_Glyph: %s", ft_errorstring(fterr));
		FT_Stroker_Done(stroker);
		return nil;
	}

	fterr = FT_Glyph_Stroke(&glyph, stroker, 1);
	if (fterr)
	{
		fz_warn("FT_Glyph_Stroke: %s", ft_errorstring(fterr));
		FT_Done_Glyph(glyph);
		FT_Stroker_Done(stroker);
		return nil;
	}

	fterr = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
	if (fterr)
	{
		fz_warn("FT_Glyph_To_Bitmap: %s", ft_errorstring(fterr));
		FT_Done_Glyph(glyph);
		FT_Stroker_Done(stroker);
		return nil;
	}

	bitmap = (FT_BitmapGlyph)glyph;
	pix = fz_newpixmap(NULL,
		bitmap->left,
		bitmap->top - bitmap->bitmap.rows,
		bitmap->bitmap.width,
		bitmap->bitmap.rows);

	for (y = 0; y < pix->h; y++)
	{
		memcpy(pix->samples + y * pix->w,
			bitmap->bitmap.buffer + (pix->h - y - 1) * bitmap->bitmap.pitch,
			pix->w);
	}

	FT_Done_Glyph(glyph);
	FT_Stroker_Done(stroker);

	return pix;
}
fz_pixmap *
fz_renderftglyph(fz_font *font, int gid, fz_matrix trm)
{
	FT_Face face = font->ftface;
	FT_Matrix m;
	FT_Vector v;
	FT_Error fterr;
	fz_pixmap *glyph;
	int y;

	/* Fudge the font matrix to stretch the glyph if we've substituted the font. */
	if (font->ftsubstitute && gid < font->widthcount)
	{
		int subw;
		int realw;
		float scale;

		/* TODO: use FT_Get_Advance */
		fterr = FT_Set_Char_Size(face, 1000, 1000, 72, 72);
		if (fterr)
			fz_warn("freetype setting character size: %s", ft_errorstring(fterr));

		fterr = FT_Load_Glyph(font->ftface, gid,
			FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM);
		if (fterr)
			fz_warn("freetype failed to load glyph: %s", ft_errorstring(fterr));

		realw = ((FT_Face)font->ftface)->glyph->metrics.horiAdvance;
		subw = font->widthtable[gid];
		if (realw)
			scale = (float) subw / realw;
		else
			scale = 1;

		trm = fz_concat(fz_scale(scale, 1), trm);
	}

	/*
	Freetype mutilates complex glyphs if they are loaded
	with FT_Set_Char_Size 1.0. it rounds the coordinates
	before applying transformation. to get more precision in
	freetype, we shift part of the scale in the matrix
	into FT_Set_Char_Size instead
	*/

	m.xx = trm.a * 64; /* should be 65536 */
	m.yx = trm.b * 64;
	m.xy = trm.c * 64;
	m.yy = trm.d * 64;
	v.x = trm.e * 64;
	v.y = trm.f * 64;

	fterr = FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */
	if (fterr)
		fz_warn("freetype setting character size: %s", ft_errorstring(fterr));
	FT_Set_Transform(face, &m, &v);

	if (font->fthint)
	{
		/*
		Enable hinting, but keep the huge char size so that
		it is hinted for a character. This will in effect nullify
		the effect of grid fitting. This form of hinting should
		only be used for DynaLab and similar tricky TrueType fonts,
		so that we get the correct outline shape.
		*/
#ifdef GRIDFIT
		/* If you really want grid fitting, enable this code. */
		float scale = fz_matrixexpansion(trm);
		m.xx = trm.a * 65536 / scale;
		m.xy = trm.b * 65536 / scale;
		m.yx = trm.c * 65536 / scale;
		m.yy = trm.d * 65536 / scale;
		v.x = 0;
		v.y = 0;

		fterr = FT_Set_Char_Size(face, 64 * scale, 64 * scale, 72, 72);
		if (fterr)
			fz_warn("freetype setting character size: %s", ft_errorstring(fterr));
		FT_Set_Transform(face, &m, &v);
#endif
		fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP);
		if (fterr)
			fz_warn("freetype load glyph (gid %d): %s", gid, ft_errorstring(fterr));
	}
	else
	{
		fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING);
		if (fterr)
		{
			fz_warn("freetype load glyph (gid %d): %s", gid, ft_errorstring(fterr));
			return nil;
		}
	}

	fterr = FT_Render_Glyph(face->glyph, ft_render_mode_normal);
	if (fterr)
	{
		fz_warn("freetype render glyph (gid %d): %s", gid, ft_errorstring(fterr));
		return nil;
	}

	glyph = fz_newpixmap(NULL,
		face->glyph->bitmap_left,
		face->glyph->bitmap_top - face->glyph->bitmap.rows,
		face->glyph->bitmap.width,
		face->glyph->bitmap.rows);

	for (y = 0; y < glyph->h; y++)
	{
		memcpy(glyph->samples + y * glyph->w,
			face->glyph->bitmap.buffer + (glyph->h - y - 1) * face->glyph->bitmap.pitch,
			glyph->w);
	}

	return glyph;
}