forked from KolibriOS/kolibrios
610 lines
13 KiB
C
610 lines
13 KiB
C
#include "fitz.h"
|
|
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
#include FT_STROKER_H
|
|
|
|
static void fz_finalize_freetype(void);
|
|
|
|
static fz_font *
|
|
fz_new_font(char *name)
|
|
{
|
|
fz_font *font;
|
|
|
|
font = fz_malloc(sizeof(fz_font));
|
|
font->refs = 1;
|
|
|
|
if (name)
|
|
fz_strlcpy(font->name, name, sizeof font->name);
|
|
else
|
|
fz_strlcpy(font->name, "(null)", sizeof font->name);
|
|
|
|
font->ft_face = NULL;
|
|
font->ft_substitute = 0;
|
|
font->ft_bold = 0;
|
|
font->ft_italic = 0;
|
|
font->ft_hint = 0;
|
|
|
|
font->ft_file = NULL;
|
|
font->ft_data = NULL;
|
|
font->ft_size = 0;
|
|
|
|
font->t3matrix = fz_identity;
|
|
font->t3resources = NULL;
|
|
font->t3procs = NULL;
|
|
font->t3widths = NULL;
|
|
font->t3xref = NULL;
|
|
font->t3run = NULL;
|
|
|
|
font->bbox.x0 = 0;
|
|
font->bbox.y0 = 0;
|
|
font->bbox.x1 = 1000;
|
|
font->bbox.y1 = 1000;
|
|
|
|
font->width_count = 0;
|
|
font->width_table = NULL;
|
|
|
|
return font;
|
|
}
|
|
|
|
fz_font *
|
|
fz_keep_font(fz_font *font)
|
|
{
|
|
font->refs ++;
|
|
return font;
|
|
}
|
|
|
|
void
|
|
fz_drop_font(fz_font *font)
|
|
{
|
|
int fterr;
|
|
int i;
|
|
|
|
if (font && --font->refs == 0)
|
|
{
|
|
if (font->t3procs)
|
|
{
|
|
if (font->t3resources)
|
|
fz_drop_obj(font->t3resources);
|
|
for (i = 0; i < 256; i++)
|
|
if (font->t3procs[i])
|
|
fz_drop_buffer(font->t3procs[i]);
|
|
fz_free(font->t3procs);
|
|
fz_free(font->t3widths);
|
|
}
|
|
|
|
if (font->ft_face)
|
|
{
|
|
fterr = FT_Done_Face((FT_Face)font->ft_face);
|
|
if (fterr)
|
|
fz_warn("freetype finalizing face: %s", ft_error_string(fterr));
|
|
fz_finalize_freetype();
|
|
}
|
|
|
|
if (font->ft_file)
|
|
fz_free(font->ft_file);
|
|
if (font->ft_data)
|
|
fz_free(font->ft_data);
|
|
|
|
if (font->width_table)
|
|
fz_free(font->width_table);
|
|
|
|
fz_free(font);
|
|
}
|
|
}
|
|
|
|
void
|
|
fz_set_font_bbox(fz_font *font, float xmin, float ymin, float xmax, float ymax)
|
|
{
|
|
font->bbox.x0 = xmin;
|
|
font->bbox.y0 = ymin;
|
|
font->bbox.x1 = xmax;
|
|
font->bbox.y1 = ymax;
|
|
}
|
|
|
|
/*
|
|
* Freetype hooks
|
|
*/
|
|
|
|
static FT_Library fz_ftlib = NULL;
|
|
static int fz_ftlib_refs = 0;
|
|
|
|
#undef __FTERRORS_H__
|
|
#define FT_ERRORDEF(e, v, s) { (e), (s) },
|
|
#define FT_ERROR_START_LIST
|
|
#define FT_ERROR_END_LIST { 0, NULL }
|
|
|
|
struct ft_error
|
|
{
|
|
int err;
|
|
char *str;
|
|
};
|
|
|
|
static const struct ft_error ft_errors[] =
|
|
{
|
|
#include FT_ERRORS_H
|
|
};
|
|
|
|
char *ft_error_string(int err)
|
|
{
|
|
const struct ft_error *e;
|
|
|
|
for (e = ft_errors; e->str != NULL; e++)
|
|
if (e->err == err)
|
|
return e->str;
|
|
|
|
return "Unknown error";
|
|
}
|
|
|
|
static fz_error
|
|
fz_init_freetype(void)
|
|
{
|
|
int fterr;
|
|
int maj, min, pat;
|
|
|
|
if (fz_ftlib)
|
|
{
|
|
fz_ftlib_refs++;
|
|
return fz_okay;
|
|
}
|
|
|
|
fterr = FT_Init_FreeType(&fz_ftlib);
|
|
if (fterr)
|
|
return fz_throw("cannot init freetype: %s", ft_error_string(fterr));
|
|
|
|
FT_Library_Version(fz_ftlib, &maj, &min, &pat);
|
|
if (maj == 2 && min == 1 && pat < 7)
|
|
{
|
|
fterr = FT_Done_FreeType(fz_ftlib);
|
|
if (fterr)
|
|
fz_warn("freetype finalizing: %s", ft_error_string(fterr));
|
|
return fz_throw("freetype version too old: %d.%d.%d", maj, min, pat);
|
|
}
|
|
|
|
fz_ftlib_refs++;
|
|
return fz_okay;
|
|
}
|
|
|
|
static void
|
|
fz_finalize_freetype(void)
|
|
{
|
|
int fterr;
|
|
|
|
if (--fz_ftlib_refs == 0)
|
|
{
|
|
fterr = FT_Done_FreeType(fz_ftlib);
|
|
if (fterr)
|
|
fz_warn("freetype finalizing: %s", ft_error_string(fterr));
|
|
fz_ftlib = NULL;
|
|
}
|
|
}
|
|
|
|
fz_error
|
|
fz_new_font_from_file(fz_font **fontp, char *path, int index)
|
|
{
|
|
FT_Face face;
|
|
fz_error error;
|
|
fz_font *font;
|
|
int fterr;
|
|
|
|
error = fz_init_freetype();
|
|
if (error)
|
|
return fz_rethrow(error, "cannot init freetype library");
|
|
|
|
fterr = FT_New_Face(fz_ftlib, path, index, &face);
|
|
if (fterr)
|
|
return fz_throw("freetype: cannot load font: %s", ft_error_string(fterr));
|
|
|
|
font = fz_new_font(face->family_name);
|
|
font->ft_face = face;
|
|
font->bbox.x0 = face->bbox.xMin * 1000 / face->units_per_EM;
|
|
font->bbox.y0 = face->bbox.yMin * 1000 / face->units_per_EM;
|
|
font->bbox.x1 = face->bbox.xMax * 1000 / face->units_per_EM;
|
|
font->bbox.y1 = face->bbox.yMax * 1000 / face->units_per_EM;
|
|
|
|
*fontp = font;
|
|
return fz_okay;
|
|
}
|
|
|
|
fz_error
|
|
fz_new_font_from_memory(fz_font **fontp, unsigned char *data, int len, int index)
|
|
{
|
|
FT_Face face;
|
|
fz_error error;
|
|
fz_font *font;
|
|
int fterr;
|
|
|
|
error = fz_init_freetype();
|
|
if (error)
|
|
return fz_rethrow(error, "cannot init freetype library");
|
|
|
|
fterr = FT_New_Memory_Face(fz_ftlib, data, len, index, &face);
|
|
if (fterr)
|
|
return fz_throw("freetype: cannot load font: %s", ft_error_string(fterr));
|
|
|
|
font = fz_new_font(face->family_name);
|
|
font->ft_face = face;
|
|
font->bbox.x0 = face->bbox.xMin * 1000 / face->units_per_EM;
|
|
font->bbox.y0 = face->bbox.yMin * 1000 / face->units_per_EM;
|
|
font->bbox.x1 = face->bbox.xMax * 1000 / face->units_per_EM;
|
|
font->bbox.y1 = face->bbox.yMax * 1000 / face->units_per_EM;
|
|
|
|
*fontp = font;
|
|
return fz_okay;
|
|
}
|
|
|
|
static fz_matrix
|
|
fz_adjust_ft_glyph_width(fz_font *font, int gid, fz_matrix trm)
|
|
{
|
|
/* Fudge the font matrix to stretch the glyph if we've substituted the font. */
|
|
if (font->ft_substitute && gid < font->width_count)
|
|
{
|
|
FT_Error fterr;
|
|
int subw;
|
|
int realw;
|
|
float scale;
|
|
|
|
/* TODO: use FT_Get_Advance */
|
|
fterr = FT_Set_Char_Size(font->ft_face, 1000, 1000, 72, 72);
|
|
if (fterr)
|
|
fz_warn("freetype setting character size: %s", ft_error_string(fterr));
|
|
|
|
fterr = FT_Load_Glyph(font->ft_face, gid,
|
|
FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM);
|
|
if (fterr)
|
|
fz_warn("freetype failed to load glyph: %s", ft_error_string(fterr));
|
|
|
|
realw = ((FT_Face)font->ft_face)->glyph->metrics.horiAdvance;
|
|
subw = font->width_table[gid];
|
|
if (realw)
|
|
scale = (float) subw / realw;
|
|
else
|
|
scale = 1;
|
|
|
|
return fz_concat(fz_scale(scale, 1), trm);
|
|
}
|
|
|
|
return trm;
|
|
}
|
|
|
|
static fz_pixmap *
|
|
fz_copy_ft_bitmap(int left, int top, FT_Bitmap *bitmap)
|
|
{
|
|
fz_pixmap *pixmap;
|
|
int y;
|
|
|
|
pixmap = fz_new_pixmap(NULL, bitmap->width, bitmap->rows);
|
|
pixmap->x = left;
|
|
pixmap->y = top - bitmap->rows;
|
|
|
|
if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO)
|
|
{
|
|
for (y = 0; y < pixmap->h; y++)
|
|
{
|
|
unsigned char *out = pixmap->samples + y * pixmap->w;
|
|
unsigned char *in = bitmap->buffer + (pixmap->h - y - 1) * bitmap->pitch;
|
|
unsigned char bit = 0x80;
|
|
int w = pixmap->w;
|
|
while (w--)
|
|
{
|
|
*out++ = (*in & bit) ? 255 : 0;
|
|
bit >>= 1;
|
|
if (bit == 0)
|
|
{
|
|
bit = 0x80;
|
|
in++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (y = 0; y < pixmap->h; y++)
|
|
{
|
|
memcpy(pixmap->samples + y * pixmap->w,
|
|
bitmap->buffer + (pixmap->h - y - 1) * bitmap->pitch,
|
|
pixmap->w);
|
|
}
|
|
}
|
|
|
|
return pixmap;
|
|
}
|
|
|
|
fz_pixmap *
|
|
fz_render_ft_glyph(fz_font *font, int gid, fz_matrix trm)
|
|
{
|
|
FT_Face face = font->ft_face;
|
|
FT_Matrix m;
|
|
FT_Vector v;
|
|
FT_Error fterr;
|
|
|
|
trm = fz_adjust_ft_glyph_width(font, gid, trm);
|
|
|
|
if (font->ft_italic)
|
|
trm = fz_concat(fz_shear(0.3f, 0), 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_error_string(fterr));
|
|
FT_Set_Transform(face, &m, &v);
|
|
|
|
if (fz_get_aa_level() == 0)
|
|
{
|
|
/* If you really want grid fitting, enable this code. */
|
|
float scale = fz_matrix_expansion(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_error_string(fterr));
|
|
FT_Set_Transform(face, &m, &v);
|
|
fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_TARGET_MONO);
|
|
if (fterr)
|
|
fz_warn("freetype load glyph (gid %d): %s", gid, ft_error_string(fterr));
|
|
}
|
|
else if (font->ft_hint)
|
|
{
|
|
/*
|
|
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.
|
|
*/
|
|
fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP);
|
|
if (fterr)
|
|
fz_warn("freetype load glyph (gid %d): %s", gid, ft_error_string(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_error_string(fterr));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (font->ft_bold)
|
|
{
|
|
float strength = fz_matrix_expansion(trm) * 0.04f;
|
|
FT_Outline_Embolden(&face->glyph->outline, strength * 64);
|
|
FT_Outline_Translate(&face->glyph->outline, -strength * 32, -strength * 32);
|
|
}
|
|
|
|
fterr = FT_Render_Glyph(face->glyph, fz_get_aa_level() > 0 ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
|
|
if (fterr)
|
|
{
|
|
fz_warn("freetype render glyph (gid %d): %s", gid, ft_error_string(fterr));
|
|
return NULL;
|
|
}
|
|
|
|
return fz_copy_ft_bitmap(face->glyph->bitmap_left, face->glyph->bitmap_top, &face->glyph->bitmap);
|
|
}
|
|
|
|
fz_pixmap *
|
|
fz_render_ft_stroked_glyph(fz_font *font, int gid, fz_matrix trm, fz_matrix ctm, fz_stroke_state *state)
|
|
{
|
|
FT_Face face = font->ft_face;
|
|
float expansion = fz_matrix_expansion(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 *pixmap;
|
|
|
|
trm = fz_adjust_ft_glyph_width(font, gid, trm);
|
|
|
|
if (font->ft_italic)
|
|
trm = fz_concat(fz_shear(0.3f, 0), trm);
|
|
|
|
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_error_string(fterr));
|
|
return NULL;
|
|
}
|
|
|
|
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_error_string(fterr));
|
|
return NULL;
|
|
}
|
|
|
|
fterr = FT_Stroker_New(fz_ftlib, &stroker);
|
|
if (fterr)
|
|
{
|
|
fz_warn("FT_Stroker_New: %s", ft_error_string(fterr));
|
|
return NULL;
|
|
}
|
|
|
|
FT_Stroker_Set(stroker, linewidth, state->start_cap, state->linejoin, state->miterlimit * 65536);
|
|
|
|
fterr = FT_Get_Glyph(face->glyph, &glyph);
|
|
if (fterr)
|
|
{
|
|
fz_warn("FT_Get_Glyph: %s", ft_error_string(fterr));
|
|
FT_Stroker_Done(stroker);
|
|
return NULL;
|
|
}
|
|
|
|
fterr = FT_Glyph_Stroke(&glyph, stroker, 1);
|
|
if (fterr)
|
|
{
|
|
fz_warn("FT_Glyph_Stroke: %s", ft_error_string(fterr));
|
|
FT_Done_Glyph(glyph);
|
|
FT_Stroker_Done(stroker);
|
|
return NULL;
|
|
}
|
|
|
|
FT_Stroker_Done(stroker);
|
|
|
|
fterr = FT_Glyph_To_Bitmap(&glyph, fz_get_aa_level() > 0 ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1);
|
|
if (fterr)
|
|
{
|
|
fz_warn("FT_Glyph_To_Bitmap: %s", ft_error_string(fterr));
|
|
FT_Done_Glyph(glyph);
|
|
return NULL;
|
|
}
|
|
|
|
bitmap = (FT_BitmapGlyph)glyph;
|
|
pixmap = fz_copy_ft_bitmap(bitmap->left, bitmap->top, &bitmap->bitmap);
|
|
FT_Done_Glyph(glyph);
|
|
|
|
return pixmap;
|
|
}
|
|
|
|
/*
|
|
* Type 3 fonts...
|
|
*/
|
|
|
|
fz_font *
|
|
fz_new_type3_font(char *name, fz_matrix matrix)
|
|
{
|
|
fz_font *font;
|
|
int i;
|
|
|
|
font = fz_new_font(name);
|
|
font->t3procs = fz_calloc(256, sizeof(fz_buffer*));
|
|
font->t3widths = fz_calloc(256, sizeof(float));
|
|
|
|
font->t3matrix = matrix;
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
font->t3procs[i] = NULL;
|
|
font->t3widths[i] = 0;
|
|
}
|
|
|
|
return font;
|
|
}
|
|
|
|
fz_pixmap *
|
|
fz_render_t3_glyph(fz_font *font, int gid, fz_matrix trm, fz_colorspace *model)
|
|
{
|
|
fz_error error;
|
|
fz_matrix ctm;
|
|
fz_buffer *contents;
|
|
fz_bbox bbox;
|
|
fz_device *dev;
|
|
fz_glyph_cache *cache;
|
|
fz_pixmap *glyph;
|
|
fz_pixmap *result;
|
|
|
|
if (gid < 0 || gid > 255)
|
|
return NULL;
|
|
|
|
contents = font->t3procs[gid];
|
|
if (!contents)
|
|
return NULL;
|
|
|
|
ctm = fz_concat(font->t3matrix, trm);
|
|
dev = fz_new_bbox_device(&bbox);
|
|
error = font->t3run(font->t3xref, font->t3resources, contents, dev, ctm);
|
|
if (error)
|
|
fz_catch(error, "cannot draw type3 glyph");
|
|
|
|
if (dev->flags & FZ_CHARPROC_MASK)
|
|
{
|
|
if (dev->flags & FZ_CHARPROC_COLOR)
|
|
fz_warn("type3 glyph claims to be both masked and colored");
|
|
model = NULL;
|
|
}
|
|
else if (dev->flags & FZ_CHARPROC_COLOR)
|
|
{
|
|
if (model == NULL)
|
|
fz_warn("colored type3 glyph wanted in masked context");
|
|
}
|
|
else
|
|
{
|
|
fz_warn("type3 glyph doesn't specify masked or colored");
|
|
model = NULL; /* Treat as masked */
|
|
}
|
|
|
|
fz_free_device(dev);
|
|
|
|
bbox.x0--;
|
|
bbox.y0--;
|
|
bbox.x1++;
|
|
bbox.y1++;
|
|
|
|
glyph = fz_new_pixmap_with_rect(model ? model : fz_device_gray, bbox);
|
|
fz_clear_pixmap(glyph);
|
|
|
|
cache = fz_new_glyph_cache();
|
|
dev = fz_new_draw_device_type3(cache, glyph);
|
|
error = font->t3run(font->t3xref, font->t3resources, contents, dev, ctm);
|
|
if (error)
|
|
fz_catch(error, "cannot draw type3 glyph");
|
|
fz_free_device(dev);
|
|
fz_free_glyph_cache(cache);
|
|
|
|
if (model == NULL)
|
|
{
|
|
result = fz_alpha_from_gray(glyph, 0);
|
|
fz_drop_pixmap(glyph);
|
|
}
|
|
else
|
|
result = glyph;
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
fz_debug_font(fz_font *font)
|
|
{
|
|
printf("font '%s' {\n", font->name);
|
|
|
|
if (font->ft_face)
|
|
{
|
|
printf("\tfreetype face %p\n", font->ft_face);
|
|
if (font->ft_substitute)
|
|
printf("\tsubstitute font\n");
|
|
}
|
|
|
|
if (font->t3procs)
|
|
{
|
|
printf("\ttype3 matrix [%g %g %g %g]\n",
|
|
font->t3matrix.a, font->t3matrix.b,
|
|
font->t3matrix.c, font->t3matrix.d);
|
|
}
|
|
|
|
printf("\tbbox [%g %g %g %g]\n",
|
|
font->bbox.x0, font->bbox.y0,
|
|
font->bbox.x1, font->bbox.y1);
|
|
|
|
printf("}\n");
|
|
}
|