#include "mupdf/fitz.h" #include "mupdf/ucdn.h" #include "fitz-imp.h" #include "font-imp.h" #include "draw-imp.h" #include #include "hb.h" #include "hb-ft.h" #include #include FT_FREETYPE_H #include FT_ADVANCES_H #include FT_MODULE_H #include FT_STROKER_H #include FT_SYSTEM_H #include FT_TRUETYPE_TABLES_H #include FT_TRUETYPE_TAGS_H #define MAX_BBOX_TABLE_SIZE 4096 #define MAX_ADVANCE_CACHE 4096 #ifndef FT_SFNT_OS2 #define FT_SFNT_OS2 ft_sfnt_os2 #endif /* 20 degrees */ #define SHEAR 0.36397f static void fz_drop_freetype(fz_context *ctx); static fz_font * fz_new_font(fz_context *ctx, const char *name, int use_glyph_bbox, int glyph_count) { fz_font *font; int i; font = fz_malloc_struct(ctx, 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->flags.ft_substitute = 0; font->flags.fake_bold = 0; font->flags.fake_italic = 0; font->flags.has_opentype = 0; font->t3matrix = fz_identity; font->t3resources = NULL; font->t3procs = NULL; font->t3lists = NULL; font->t3widths = NULL; font->t3flags = NULL; font->t3doc = NULL; font->t3run = NULL; font->bbox.x0 = 0; font->bbox.y0 = 0; font->bbox.x1 = 1; font->bbox.y1 = 1; font->glyph_count = glyph_count; if (use_glyph_bbox && glyph_count <= MAX_BBOX_TABLE_SIZE) { font->bbox_table = fz_malloc_array(ctx, glyph_count, sizeof(fz_rect)); for (i = 0; i < glyph_count; i++) font->bbox_table[i] = fz_infinite_rect; } else { font->bbox_table = NULL; } font->width_count = 0; font->width_table = NULL; return font; } fz_font * fz_keep_font(fz_context *ctx, fz_font *font) { return fz_keep_imp(ctx, font, &font->refs); } static void free_resources(fz_context *ctx, fz_font *font) { int i; if (font->t3resources) { font->t3freeres(ctx, font->t3doc, font->t3resources); font->t3resources = NULL; } if (font->t3procs) { for (i = 0; i < 256; i++) fz_drop_buffer(ctx, font->t3procs[i]); } fz_free(ctx, font->t3procs); font->t3procs = NULL; } void fz_decouple_type3_font(fz_context *ctx, fz_font *font, void *t3doc) { if (!font || !t3doc || font->t3doc == NULL) return; if (font->t3doc != t3doc) fz_throw(ctx, FZ_ERROR_GENERIC, "can't decouple type3 font from a different doc"); font->t3doc = NULL; free_resources(ctx, font); } void fz_drop_font(fz_context *ctx, fz_font *font) { int fterr; int i; if (!fz_drop_imp(ctx, font, &font->refs)) return; if (font->t3lists) { free_resources(ctx, font); for (i = 0; i < 256; i++) fz_drop_display_list(ctx, font->t3lists[i]); fz_free(ctx, font->t3procs); fz_free(ctx, font->t3lists); fz_free(ctx, font->t3widths); fz_free(ctx, font->t3flags); } if (font->ft_face) { fz_lock(ctx, FZ_LOCK_FREETYPE); fterr = FT_Done_Face((FT_Face)font->ft_face); fz_unlock(ctx, FZ_LOCK_FREETYPE); if (fterr) fz_warn(ctx, "freetype finalizing face: %s", ft_error_string(fterr)); fz_drop_freetype(ctx); } for (i = 0; i < 256; ++i) fz_free(ctx, font->encoding_cache[i]); fz_drop_buffer(ctx, font->buffer); fz_free(ctx, font->bbox_table); fz_free(ctx, font->width_table); fz_free(ctx, font->advance_cache); if (font->shaper_data.destroy && font->shaper_data.shaper_handle) { font->shaper_data.destroy(ctx, font->shaper_data.shaper_handle); } fz_free(ctx, font); } void fz_set_font_bbox(fz_context *ctx, fz_font *font, float xmin, float ymin, float xmax, float ymax) { if (xmin >= xmax || ymin >= ymax) { /* Invalid bbox supplied. */ if (font->t3procs) { /* For type3 fonts we use the union of all the glyphs' bboxes. */ font->bbox = fz_empty_rect; } else { /* For other fonts it would be prohibitively slow to measure the true one, so make one up. */ font->bbox = fz_unit_rect; } font->flags.invalid_bbox = 1; } else { font->bbox.x0 = xmin; font->bbox.y0 = ymin; font->bbox.x1 = xmax; font->bbox.y1 = ymax; } } float fz_font_ascender(fz_context *ctx, fz_font *font) { if (font->t3procs) return font->bbox.y1; else { FT_Face face = font->ft_face; return (float)face->ascender / face->units_per_EM; } } float fz_font_descender(fz_context *ctx, fz_font *font) { if (font->t3procs) return font->bbox.y0; else { FT_Face face = font->ft_face; return (float)face->descender / face->units_per_EM; } } /* * Freetype hooks */ struct fz_font_context_s { int ctx_refs; FT_Library ftlib; struct FT_MemoryRec_ ftmemory; int ftlib_refs; fz_load_system_font_fn *load_font; fz_load_system_cjk_font_fn *load_cjk_font; fz_load_system_fallback_font_fn *load_fallback_font; /* Cached fallback fonts */ struct { fz_font *serif, *sans; } fallback[256]; fz_font *symbol; fz_font *emoji; }; #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 void *ft_alloc(FT_Memory memory, long size) { fz_context *ctx = (fz_context *) memory->user; return fz_malloc_no_throw(ctx, size); } static void ft_free(FT_Memory memory, void *block) { fz_context *ctx = (fz_context *) memory->user; fz_free(ctx, block); } static void *ft_realloc(FT_Memory memory, long cur_size, long new_size, void *block) { fz_context *ctx = (fz_context *) memory->user; void *newblock = NULL; if (new_size == 0) { fz_free(ctx, block); return newblock; } if (block == NULL) return ft_alloc(memory, new_size); return fz_resize_array_no_throw(ctx, block, 1, new_size); } void fz_new_font_context(fz_context *ctx) { ctx->font = fz_malloc_struct(ctx, fz_font_context); ctx->font->ctx_refs = 1; ctx->font->ftlib = NULL; ctx->font->ftlib_refs = 0; ctx->font->load_font = NULL; ctx->font->ftmemory.user = ctx; ctx->font->ftmemory.alloc = ft_alloc; ctx->font->ftmemory.free = ft_free; ctx->font->ftmemory.realloc = ft_realloc; } fz_font_context * fz_keep_font_context(fz_context *ctx) { if (!ctx) return NULL; return fz_keep_imp(ctx, ctx->font, &ctx->font->ctx_refs); } void fz_drop_font_context(fz_context *ctx) { if (!ctx) return; if (fz_drop_imp(ctx, ctx->font, &ctx->font->ctx_refs)) { int i; for (i = 0; i < nelem(ctx->font->fallback); ++i) { fz_drop_font(ctx, ctx->font->fallback[i].serif); fz_drop_font(ctx, ctx->font->fallback[i].sans); } fz_drop_font(ctx, ctx->font->symbol); fz_drop_font(ctx, ctx->font->emoji); fz_free(ctx, ctx->font); ctx->font = NULL; } } void fz_install_load_system_font_funcs(fz_context *ctx, fz_load_system_font_fn *f, fz_load_system_cjk_font_fn *f_cjk, fz_load_system_fallback_font_fn *f_back) { ctx->font->load_font = f; ctx->font->load_cjk_font = f_cjk; ctx->font->load_fallback_font = f_back; } fz_font *fz_load_system_font(fz_context *ctx, const char *name, int bold, int italic, int needs_exact_metrics) { fz_font *font = NULL; if (ctx->font->load_font) { fz_try(ctx) font = ctx->font->load_font(ctx, name, bold, italic, needs_exact_metrics); fz_catch(ctx) font = NULL; } return font; } fz_font *fz_load_system_cjk_font(fz_context *ctx, const char *name, int ros, int serif) { fz_font *font = NULL; if (ctx->font->load_cjk_font) { fz_try(ctx) font = ctx->font->load_cjk_font(ctx, name, ros, serif); fz_catch(ctx) font = NULL; } return font; } fz_font *fz_load_system_fallback_font(fz_context *ctx, int script, int language, int serif, int bold, int italic) { fz_font *font = NULL; if (ctx->font->load_fallback_font) { fz_try(ctx) font = ctx->font->load_fallback_font(ctx, script, language, serif, bold, italic); fz_catch(ctx) font = NULL; } return font; } fz_font *fz_load_fallback_font(fz_context *ctx, int script, int language, int serif, int bold, int italic) { fz_font **fontp; const unsigned char *data; int index; int subfont; int size; if (script < 0 || script > nelem(ctx->font->fallback)) return NULL; /* TODO: bold and italic */ index = script; if (script == UCDN_SCRIPT_HAN) { switch (language) { case FZ_LANG_ja: index = UCDN_LAST_SCRIPT + 1; break; case FZ_LANG_ko: index = UCDN_LAST_SCRIPT + 2; break; case FZ_LANG_zh_Hans: index = UCDN_LAST_SCRIPT + 3; break; case FZ_LANG_zh_Hant: index = UCDN_LAST_SCRIPT + 4; break; } } if (script == UCDN_SCRIPT_ARABIC) { if (language == FZ_LANG_ur || language == FZ_LANG_urd) index = UCDN_LAST_SCRIPT + 5; } if (serif) fontp = &ctx->font->fallback[index].serif; else fontp = &ctx->font->fallback[index].sans; if (!*fontp) { *fontp = fz_load_system_fallback_font(ctx, script, language, serif, bold, italic); if (!*fontp) { data = fz_lookup_noto_font(ctx, script, language, serif, &size, &subfont); if (data) *fontp = fz_new_font_from_memory(ctx, NULL, data, size, subfont, 0); } } return *fontp; } fz_font *fz_load_fallback_symbol_font(fz_context *ctx) { const unsigned char *data; int size; if (!ctx->font->symbol) { data = fz_lookup_noto_symbol_font(ctx, &size); if (data) ctx->font->symbol = fz_new_font_from_memory(ctx, NULL, data, size, 0, 0); } return ctx->font->symbol; } fz_font *fz_load_fallback_emoji_font(fz_context *ctx) { const unsigned char *data; int size; if (!ctx->font->emoji) { data = fz_lookup_noto_emoji_font(ctx, &size); if (data) ctx->font->emoji = fz_new_font_from_memory(ctx, NULL, data, size, 0, 0); } return ctx->font->emoji; } static const struct ft_error ft_errors[] = { #include FT_ERRORS_H }; const char *ft_error_string(int err) { const struct ft_error *e; for (e = ft_errors; e->str; e++) if (e->err == err) return e->str; return "Unknown error"; } static void fz_keep_freetype(fz_context *ctx) { int fterr; int maj, min, pat; fz_font_context *fct = ctx->font; fz_lock(ctx, FZ_LOCK_FREETYPE); if (fct->ftlib) { fct->ftlib_refs++; fz_unlock(ctx, FZ_LOCK_FREETYPE); return; } fterr = FT_New_Library(&fct->ftmemory, &fct->ftlib); if (fterr) { const char *mess = ft_error_string(fterr); fz_unlock(ctx, FZ_LOCK_FREETYPE); fz_throw(ctx, FZ_ERROR_GENERIC, "cannot init freetype: %s", mess); } FT_Add_Default_Modules(fct->ftlib); FT_Library_Version(fct->ftlib, &maj, &min, &pat); if (maj == 2 && min == 1 && pat < 7) { fterr = FT_Done_Library(fct->ftlib); if (fterr) fz_warn(ctx, "freetype finalizing: %s", ft_error_string(fterr)); fz_unlock(ctx, FZ_LOCK_FREETYPE); fz_throw(ctx, FZ_ERROR_GENERIC, "freetype version too old: %d.%d.%d", maj, min, pat); } fct->ftlib_refs++; fz_unlock(ctx, FZ_LOCK_FREETYPE); } static void fz_drop_freetype(fz_context *ctx) { int fterr; fz_font_context *fct = ctx->font; fz_lock(ctx, FZ_LOCK_FREETYPE); if (--fct->ftlib_refs == 0) { fterr = FT_Done_Library(fct->ftlib); if (fterr) fz_warn(ctx, "freetype finalizing: %s", ft_error_string(fterr)); fct->ftlib = NULL; } fz_unlock(ctx, FZ_LOCK_FREETYPE); } fz_font * fz_new_font_from_buffer(fz_context *ctx, const char *name, fz_buffer *buffer, int index, int use_glyph_bbox) { FT_Face face; TT_OS2 *os2; fz_font *font; int fterr; FT_ULong tag, size, i, n; char namebuf[sizeof(font->name)]; fz_keep_freetype(ctx); fz_lock(ctx, FZ_LOCK_FREETYPE); fterr = FT_New_Memory_Face(ctx->font->ftlib, buffer->data, (FT_Long)buffer->len, index, &face); fz_unlock(ctx, FZ_LOCK_FREETYPE); if (fterr) { fz_drop_freetype(ctx); fz_throw(ctx, FZ_ERROR_GENERIC, "freetype: cannot load font: %s", ft_error_string(fterr)); } if (!name) { if (!face->family_name) { name = face->style_name; } else if (!face->style_name) { name = face->family_name; } else if (strstr(face->style_name, face->family_name) == face->style_name) { name = face->style_name; } else { fz_strlcpy(namebuf, face->family_name, sizeof(namebuf)); fz_strlcat(namebuf, " ", sizeof(namebuf)); fz_strlcat(namebuf, face->style_name, sizeof(namebuf)); name = namebuf; } } font = fz_new_font(ctx, name, use_glyph_bbox, face->num_glyphs); font->ft_face = face; fz_set_font_bbox(ctx, font, (float) face->bbox.xMin / face->units_per_EM, (float) face->bbox.yMin / face->units_per_EM, (float) face->bbox.xMax / face->units_per_EM, (float) face->bbox.yMax / face->units_per_EM); font->flags.is_mono = !!(face->face_flags & FT_FACE_FLAG_FIXED_WIDTH); font->flags.is_serif = 1; font->flags.is_bold = !!(face->style_flags & FT_STYLE_FLAG_BOLD); font->flags.is_italic = !!(face->style_flags & FT_STYLE_FLAG_ITALIC); if (FT_IS_SFNT(face)) { os2 = FT_Get_Sfnt_Table(face, FT_SFNT_OS2); if (os2) font->flags.is_serif = !(os2->sFamilyClass & 2048); /* Class 8 is sans-serif */ FT_Sfnt_Table_Info(face, 0, NULL, &n); for (i = 0; i < n; ++i) { FT_Sfnt_Table_Info(face, i, &tag, &size); if (tag == TTAG_GDEF || tag == TTAG_GPOS || tag == TTAG_GSUB) font->flags.has_opentype = 1; } } if (name) { if (!font->flags.is_bold) { if (strstr(name, "Semibold")) font->flags.is_bold = 1; if (strstr(name, "Bold")) font->flags.is_bold = 1; } if (!font->flags.is_italic) { if (strstr(name, "Italic")) font->flags.is_italic = 1; if (strstr(name, "Oblique")) font->flags.is_italic = 1; } } font->buffer = fz_keep_buffer(ctx, buffer); return font; } fz_font * fz_new_font_from_memory(fz_context *ctx, const char *name, const unsigned char *data, int len, int index, int use_glyph_bbox) { fz_buffer *buffer = fz_new_buffer_from_shared_data(ctx, data, len); fz_font *font = NULL; fz_try(ctx) font = fz_new_font_from_buffer(ctx, name, buffer, index, use_glyph_bbox); fz_always(ctx) fz_drop_buffer(ctx, buffer); fz_catch(ctx) fz_rethrow(ctx); return font; } fz_font * fz_new_font_from_file(fz_context *ctx, const char *name, const char *path, int index, int use_glyph_bbox) { fz_buffer *buffer = fz_read_file(ctx, path); fz_font *font = NULL; fz_try(ctx) font = fz_new_font_from_buffer(ctx, name, buffer, index, use_glyph_bbox); fz_always(ctx) fz_drop_buffer(ctx, buffer); fz_catch(ctx) fz_rethrow(ctx); return font; } fz_font * fz_new_base14_font(fz_context *ctx, const char *name) { const unsigned char *data; int size; data = fz_lookup_base14_font(ctx, name, &size); if (!data) fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find builtin font with name '%s'", name); return fz_new_font_from_memory(ctx, name, data, size, 0, 0); } fz_font * fz_new_cjk_font(fz_context *ctx, int ordering, int serif) { const unsigned char *data; int size, index; data = fz_lookup_cjk_font(ctx, ordering, serif, &size, &index); if (!data) fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find builtin CJK font"); return fz_new_font_from_memory(ctx, NULL, data, size, index, 0); } fz_font * fz_new_builtin_font(fz_context *ctx, const char *name, int is_bold, int is_italic) { const unsigned char *data; int size; data = fz_lookup_builtin_font(ctx, name, is_bold, is_italic, &size); if (!data) fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find builtin font with name '%s'", name); return fz_new_font_from_memory(ctx, NULL, data, size, 0, 0); } static fz_matrix * fz_adjust_ft_glyph_width(fz_context *ctx, fz_font *font, int gid, fz_matrix *trm) { /* Fudge the font matrix to stretch the glyph if we've substituted the font. */ if (font->flags.ft_stretch && font->width_table /* && font->wmode == 0 */) { FT_Error fterr; FT_Fixed adv = 0; float subw; float realw; fz_lock(ctx, FZ_LOCK_FREETYPE); fterr = FT_Get_Advance(font->ft_face, gid, FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM, &adv); fz_unlock(ctx, FZ_LOCK_FREETYPE); if (fterr) fz_warn(ctx, "freetype getting character advance: %s", ft_error_string(fterr)); realw = adv * 1000.0f / ((FT_Face)font->ft_face)->units_per_EM; if (gid < font->width_count) subw = font->width_table[gid]; else subw = font->width_default; /* Sanity check scaling in case of broken metrics. */ if (realw > 0 && subw > 0) fz_pre_scale(trm, subw / realw, 1); } return trm; } static fz_glyph * glyph_from_ft_bitmap(fz_context *ctx, int left, int top, FT_Bitmap *bitmap) { if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) return fz_new_glyph_from_1bpp_data(ctx, left, top - bitmap->rows, bitmap->width, bitmap->rows, bitmap->buffer + (bitmap->rows-1)*bitmap->pitch, -bitmap->pitch); else return fz_new_glyph_from_8bpp_data(ctx, left, top - bitmap->rows, bitmap->width, bitmap->rows, bitmap->buffer + (bitmap->rows-1)*bitmap->pitch, -bitmap->pitch); } static fz_pixmap * pixmap_from_ft_bitmap(fz_context *ctx, int left, int top, FT_Bitmap *bitmap) { if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) return fz_new_pixmap_from_1bpp_data(ctx, left, top - bitmap->rows, bitmap->width, bitmap->rows, bitmap->buffer + (bitmap->rows-1)*bitmap->pitch, -bitmap->pitch); else return fz_new_pixmap_from_8bpp_data(ctx, left, top - bitmap->rows, bitmap->width, bitmap->rows, bitmap->buffer + (bitmap->rows-1)*bitmap->pitch, -bitmap->pitch); } /* Takes the freetype lock, and returns with it held */ static FT_GlyphSlot do_ft_render_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, int aa) { FT_Face face = font->ft_face; FT_Matrix m; FT_Vector v; FT_Error fterr; fz_matrix local_trm = *trm; float strength = fz_matrix_expansion(trm) * 0.02f; fz_adjust_ft_glyph_width(ctx, font, gid, &local_trm); if (font->flags.fake_italic) fz_pre_shear(&local_trm, SHEAR, 0); /* 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 = local_trm.a * 64; /* should be 65536 */ m.yx = local_trm.b * 64; m.xy = local_trm.c * 64; m.yy = local_trm.d * 64; v.x = local_trm.e * 64; v.y = local_trm.f * 64; fz_lock(ctx, FZ_LOCK_FREETYPE); fterr = FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */ if (fterr) fz_warn(ctx, "freetype setting character size: %s", ft_error_string(fterr)); FT_Set_Transform(face, &m, &v); if (aa == 0) { /* enable grid fitting for non-antialiased rendering */ float scale = fz_matrix_expansion(&local_trm); m.xx = local_trm.a * 65536 / scale; m.yx = local_trm.b * 65536 / scale; m.xy = local_trm.c * 65536 / scale; m.yy = local_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(ctx, "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(ctx, "freetype load hinted glyph (gid %d): %s", gid, ft_error_string(fterr)); goto retry_unhinted; } } else { retry_unhinted: fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING); if (fterr) { fz_warn(ctx, "freetype load glyph (gid %d): %s", gid, ft_error_string(fterr)); return NULL; } } if (font->flags.fake_bold) { FT_Outline_Embolden(&face->glyph->outline, strength * 64); FT_Outline_Translate(&face->glyph->outline, -strength * 32, -strength * 32); } fterr = FT_Render_Glyph(face->glyph, aa > 0 ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); if (fterr) { fz_warn(ctx, "freetype render glyph (gid %d): %s", gid, ft_error_string(fterr)); return NULL; } return face->glyph; } fz_pixmap * fz_render_ft_glyph_pixmap(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, int aa) { FT_GlyphSlot slot = do_ft_render_glyph(ctx, font, gid, trm, aa); fz_pixmap *pixmap = NULL; if (slot == NULL) { fz_unlock(ctx, FZ_LOCK_FREETYPE); return NULL; } fz_try(ctx) { pixmap = pixmap_from_ft_bitmap(ctx, slot->bitmap_left, slot->bitmap_top, &slot->bitmap); } fz_always(ctx) { fz_unlock(ctx, FZ_LOCK_FREETYPE); } fz_catch(ctx) { fz_rethrow(ctx); } return pixmap; } /* The glyph cache lock is always taken when this is called. */ fz_glyph * fz_render_ft_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, int aa) { FT_GlyphSlot slot = do_ft_render_glyph(ctx, font, gid, trm, aa); fz_glyph *glyph = NULL; if (slot == NULL) { fz_unlock(ctx, FZ_LOCK_FREETYPE); return NULL; } fz_try(ctx) { glyph = glyph_from_ft_bitmap(ctx, slot->bitmap_left, slot->bitmap_top, &slot->bitmap); } fz_always(ctx) { fz_unlock(ctx, FZ_LOCK_FREETYPE); } fz_catch(ctx) { fz_rethrow(ctx); } return glyph; } /* Takes the freetype lock, and returns with it held */ static FT_Glyph do_render_ft_stroked_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, const fz_matrix *ctm, const fz_stroke_state *state, int aa) { 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_Stroker_LineJoin line_join; FT_Stroker_LineCap line_cap; fz_matrix local_trm = *trm; fz_adjust_ft_glyph_width(ctx, font, gid, &local_trm); if (font->flags.fake_italic) fz_pre_shear(&local_trm, SHEAR, 0); m.xx = local_trm.a * 64; /* should be 65536 */ m.yx = local_trm.b * 64; m.xy = local_trm.c * 64; m.yy = local_trm.d * 64; v.x = local_trm.e * 64; v.y = local_trm.f * 64; fz_lock(ctx, FZ_LOCK_FREETYPE); fterr = FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */ if (fterr) { fz_warn(ctx, "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(ctx, "FT_Load_Glyph(gid %d): %s", gid, ft_error_string(fterr)); return NULL; } fterr = FT_Stroker_New(ctx->font->ftlib, &stroker); if (fterr) { fz_warn(ctx, "FT_Stroker_New: %s", ft_error_string(fterr)); return NULL; } line_join = state->linejoin == FZ_LINEJOIN_MITER ? FT_STROKER_LINEJOIN_MITER_FIXED : state->linejoin == FZ_LINEJOIN_ROUND ? FT_STROKER_LINEJOIN_ROUND : state->linejoin == FZ_LINEJOIN_BEVEL ? FT_STROKER_LINEJOIN_BEVEL : FT_STROKER_LINEJOIN_MITER_VARIABLE; line_cap = state->start_cap == FZ_LINECAP_BUTT ? FT_STROKER_LINECAP_BUTT : state->start_cap == FZ_LINECAP_ROUND ? FT_STROKER_LINECAP_ROUND : state->start_cap == FZ_LINECAP_SQUARE ? FT_STROKER_LINECAP_SQUARE : state->start_cap == FZ_LINECAP_TRIANGLE ? FT_STROKER_LINECAP_BUTT : FT_STROKER_LINECAP_BUTT; FT_Stroker_Set(stroker, linewidth, line_cap, line_join, state->miterlimit * 65536); fterr = FT_Get_Glyph(face->glyph, &glyph); if (fterr) { fz_warn(ctx, "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(ctx, "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, aa > 0 ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1); if (fterr) { fz_warn(ctx, "FT_Glyph_To_Bitmap: %s", ft_error_string(fterr)); FT_Done_Glyph(glyph); return NULL; } return glyph; } fz_pixmap * fz_render_ft_stroked_glyph_pixmap(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, const fz_matrix *ctm, const fz_stroke_state *state, int aa) { FT_Glyph glyph = do_render_ft_stroked_glyph(ctx, font, gid, trm, ctm, state, aa); FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyph; fz_pixmap *pixmap = NULL; if (bitmap == NULL) { fz_unlock(ctx, FZ_LOCK_FREETYPE); return NULL; } fz_try(ctx) { pixmap = pixmap_from_ft_bitmap(ctx, bitmap->left, bitmap->top, &bitmap->bitmap); } fz_always(ctx) { FT_Done_Glyph(glyph); fz_unlock(ctx, FZ_LOCK_FREETYPE); } fz_catch(ctx) { fz_rethrow(ctx); } return pixmap; } fz_glyph * fz_render_ft_stroked_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, const fz_matrix *ctm, const fz_stroke_state *state, int aa) { FT_Glyph glyph = do_render_ft_stroked_glyph(ctx, font, gid, trm, ctm, state, aa); FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyph; fz_glyph *result = NULL; if (bitmap == NULL) { fz_unlock(ctx, FZ_LOCK_FREETYPE); return NULL; } fz_try(ctx) { result = glyph_from_ft_bitmap(ctx, bitmap->left, bitmap->top, &bitmap->bitmap); } fz_always(ctx) { FT_Done_Glyph(glyph); fz_unlock(ctx, FZ_LOCK_FREETYPE); } fz_catch(ctx) { fz_rethrow(ctx); } return result; } static fz_rect * fz_bound_ft_glyph(fz_context *ctx, fz_font *font, int gid) { FT_Face face = font->ft_face; FT_Error fterr; FT_BBox cbox; FT_Matrix m; FT_Vector v; fz_rect *bounds = &font->bbox_table[gid]; // TODO: refactor loading into fz_load_ft_glyph // TODO: cache results const int scale = face->units_per_EM; const float recip = 1.0f / scale; const float strength = 0.02f; fz_matrix local_trm = fz_identity; fz_adjust_ft_glyph_width(ctx, font, gid, &local_trm); if (font->flags.fake_italic) fz_pre_shear(&local_trm, SHEAR, 0); m.xx = local_trm.a * 65536; m.yx = local_trm.b * 65536; m.xy = local_trm.c * 65536; m.yy = local_trm.d * 65536; v.x = local_trm.e * 65536; v.y = local_trm.f * 65536; fz_lock(ctx, FZ_LOCK_FREETYPE); /* Set the char size to scale=face->units_per_EM to effectively give * us unscaled results. This avoids quantisation. We then apply the * scale ourselves below. */ fterr = FT_Set_Char_Size(face, scale, scale, 72, 72); if (fterr) fz_warn(ctx, "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_NO_HINTING); if (fterr) { fz_warn(ctx, "freetype load glyph (gid %d): %s", gid, ft_error_string(fterr)); fz_unlock(ctx, FZ_LOCK_FREETYPE); bounds->x0 = bounds->x1 = local_trm.e; bounds->y0 = bounds->y1 = local_trm.f; return bounds; } if (font->flags.fake_bold) { FT_Outline_Embolden(&face->glyph->outline, strength * scale); FT_Outline_Translate(&face->glyph->outline, -strength * 0.5f * scale, -strength * 0.5f * scale); } FT_Outline_Get_CBox(&face->glyph->outline, &cbox); fz_unlock(ctx, FZ_LOCK_FREETYPE); bounds->x0 = cbox.xMin * recip; bounds->y0 = cbox.yMin * recip; bounds->x1 = cbox.xMax * recip; bounds->y1 = cbox.yMax * recip; if (fz_is_empty_rect(bounds)) { bounds->x0 = bounds->x1 = local_trm.e; bounds->y0 = bounds->y1 = local_trm.f; } return bounds; } /* Turn FT_Outline into a fz_path */ struct closure { fz_context *ctx; fz_path *path; fz_matrix trm; }; static int move_to(const FT_Vector *p, void *cc_) { struct closure *cc = (struct closure *)cc_; fz_context *ctx = cc->ctx; fz_path *path = cc->path; fz_point pt; fz_transform_point_xy(&pt, &cc->trm, p->x, p->y); fz_moveto(ctx, path, pt.x, pt.y); return 0; } static int line_to(const FT_Vector *p, void *cc_) { struct closure *cc = (struct closure *)cc_; fz_context *ctx = cc->ctx; fz_path *path = cc->path; fz_point pt; fz_transform_point_xy(&pt, &cc->trm, p->x, p->y); fz_lineto(ctx, path, pt.x, pt.y); return 0; } static int conic_to(const FT_Vector *c, const FT_Vector *p, void *cc_) { struct closure *cc = (struct closure *)cc_; fz_context *ctx = cc->ctx; fz_path *path = cc->path; fz_point ct, pt; fz_transform_point_xy(&ct, &cc->trm, c->x, c->y); fz_transform_point_xy(&pt, &cc->trm, p->x, p->y); fz_quadto(ctx, path, ct.x, ct.y, pt.x, pt.y); return 0; } static int cubic_to(const FT_Vector *c1, const FT_Vector *c2, const FT_Vector *p, void *cc_) { struct closure *cc = (struct closure *)cc_; fz_context *ctx = cc->ctx; fz_path *path = cc->path; fz_point c1t, c2t, pt; fz_transform_point_xy(&c1t, &cc->trm, c1->x, c1->y); fz_transform_point_xy(&c2t, &cc->trm, c2->x, c2->y); fz_transform_point_xy(&pt, &cc->trm, p->x, p->y); fz_curveto(ctx, path, c1t.x, c1t.y, c2t.x, c2t.y, pt.x, pt.y); return 0; } static const FT_Outline_Funcs outline_funcs = { move_to, line_to, conic_to, cubic_to, 0, 0 }; fz_path * fz_outline_ft_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm) { struct closure cc; FT_Face face = font->ft_face; int fterr; fz_matrix local_trm = *trm; const int scale = face->units_per_EM; const float recip = 1.0f / scale; const float strength = 0.02f; fz_adjust_ft_glyph_width(ctx, font, gid, &local_trm); if (font->flags.fake_italic) fz_pre_shear(&local_trm, SHEAR, 0); fz_lock(ctx, FZ_LOCK_FREETYPE); fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_SCALE | FT_LOAD_IGNORE_TRANSFORM); if (fterr) { fz_warn(ctx, "freetype load glyph (gid %d): %s", gid, ft_error_string(fterr)); fz_unlock(ctx, FZ_LOCK_FREETYPE); return NULL; } if (font->flags.fake_bold) { FT_Outline_Embolden(&face->glyph->outline, strength * scale); FT_Outline_Translate(&face->glyph->outline, -strength * 0.5f * scale, -strength * 0.5f * scale); } cc.path = NULL; fz_try(ctx) { cc.ctx = ctx; cc.path = fz_new_path(ctx); fz_concat(&cc.trm, fz_scale(&cc.trm, recip, recip), &local_trm); fz_moveto(ctx, cc.path, cc.trm.e, cc.trm.f); FT_Outline_Decompose(&face->glyph->outline, &outline_funcs, &cc); fz_closepath(ctx, cc.path); } fz_always(ctx) { fz_unlock(ctx, FZ_LOCK_FREETYPE); } fz_catch(ctx) { fz_warn(ctx, "freetype cannot decompose outline"); fz_free(ctx, cc.path); return NULL; } return cc.path; } /* * Type 3 fonts... */ fz_font * fz_new_type3_font(fz_context *ctx, const char *name, const fz_matrix *matrix) { fz_font *font; font = fz_new_font(ctx, name, 1, 256); fz_try(ctx) { font->t3procs = fz_calloc(ctx, 256, sizeof(fz_buffer*)); font->t3lists = fz_calloc(ctx, 256, sizeof(fz_display_list*)); font->t3widths = fz_calloc(ctx, 256, sizeof(float)); font->t3flags = fz_calloc(ctx, 256, sizeof(unsigned short)); } fz_catch(ctx) { fz_drop_font(ctx, font); fz_rethrow(ctx); } font->t3matrix = *matrix; return font; } static void fz_bound_t3_glyph(fz_context *ctx, fz_font *font, int gid) { fz_display_list *list; fz_device *dev; list = font->t3lists[gid]; if (!list) { font->bbox_table[gid] = fz_empty_rect; return; } dev = fz_new_bbox_device(ctx, &font->bbox_table[gid]); fz_try(ctx) { fz_run_display_list(ctx, list, dev, &font->t3matrix, &fz_infinite_rect, NULL); fz_close_device(ctx, dev); } fz_always(ctx) { fz_drop_device(ctx, dev); } fz_catch(ctx) { fz_rethrow(ctx); } /* Update font bbox with glyph's computed bbox if the font bbox is invalid */ if (font->flags.invalid_bbox) fz_union_rect(&font->bbox, &font->bbox_table[gid]); } void fz_prepare_t3_glyph(fz_context *ctx, fz_font *font, int gid, int nested_depth) { fz_buffer *contents; fz_device *dev; fz_rect d1_rect; contents = font->t3procs[gid]; if (!contents) return; /* We've not already loaded this one! */ assert(font->t3lists[gid] == NULL); font->t3lists[gid] = fz_new_display_list(ctx, &font->bbox); dev = fz_new_list_device(ctx, font->t3lists[gid]); dev->flags = FZ_DEVFLAG_FILLCOLOR_UNDEFINED | FZ_DEVFLAG_STROKECOLOR_UNDEFINED | FZ_DEVFLAG_STARTCAP_UNDEFINED | FZ_DEVFLAG_DASHCAP_UNDEFINED | FZ_DEVFLAG_ENDCAP_UNDEFINED | FZ_DEVFLAG_LINEJOIN_UNDEFINED | FZ_DEVFLAG_MITERLIMIT_UNDEFINED | FZ_DEVFLAG_LINEWIDTH_UNDEFINED; fz_try(ctx) { font->t3run(ctx, font->t3doc, font->t3resources, contents, dev, &fz_identity, NULL, 0, NULL); fz_close_device(ctx, dev); font->t3flags[gid] = dev->flags; d1_rect = dev->d1_rect; } fz_always(ctx) fz_drop_device(ctx, dev); fz_catch(ctx) fz_rethrow(ctx); if (fz_display_list_is_empty(ctx, font->t3lists[gid])) { /* If empty, no need for a huge bbox, especially as the logic * in the 'else if' can make it huge. */ font->bbox_table[gid].x0 = font->bbox.x0; font->bbox_table[gid].y0 = font->bbox.y0; font->bbox_table[gid].x1 = font->bbox.x0 + .00001f; font->bbox_table[gid].y1 = font->bbox.y0 + .00001f; } else if (font->t3flags[gid] & FZ_DEVFLAG_BBOX_DEFINED) { assert(font->bbox_table != NULL); assert(font->glyph_count > gid); font->bbox_table[gid] = d1_rect; fz_transform_rect(&font->bbox_table[gid], &font->t3matrix); if (font->flags.invalid_bbox || !fz_contains_rect(&font->bbox, &d1_rect)) { /* Either the font bbox is invalid, or the d1_rect returned is * incompatible with it. Either way, don't trust the d1 rect * and calculate it from the contents. */ fz_bound_t3_glyph(ctx, font, gid); } } else { /* No bbox has been defined for this glyph, so compute it. */ fz_bound_t3_glyph(ctx, font, gid); } } void fz_run_t3_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, fz_device *dev) { fz_display_list *list; fz_matrix ctm; list = font->t3lists[gid]; if (!list) return; fz_concat(&ctm, &font->t3matrix, trm); fz_run_display_list(ctx, list, dev, &ctm, &fz_infinite_rect, NULL); } fz_pixmap * fz_render_t3_glyph_pixmap(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, fz_colorspace *model, const fz_irect *scissor, int aa) { fz_display_list *list; fz_rect bounds; fz_irect bbox; fz_device *dev = NULL; fz_pixmap *glyph; fz_pixmap *result = NULL; if (gid < 0 || gid > 255) return NULL; list = font->t3lists[gid]; if (!list) return NULL; if (font->t3flags[gid] & FZ_DEVFLAG_MASK) { if (font->t3flags[gid] & FZ_DEVFLAG_COLOR) fz_warn(ctx, "type3 glyph claims to be both masked and colored"); model = NULL; } else if (font->t3flags[gid] & FZ_DEVFLAG_COLOR) { if (!model) fz_warn(ctx, "colored type3 glyph wanted in masked context"); } else { fz_warn(ctx, "type3 glyph doesn't specify masked or colored"); model = NULL; /* Treat as masked */ } fz_expand_rect(fz_bound_glyph(ctx, font, gid, trm, &bounds), 1); fz_irect_from_rect(&bbox, &bounds); fz_intersect_irect(&bbox, scissor); /* Glyphs must always have alpha */ glyph = fz_new_pixmap_with_bbox(ctx, model, &bbox, NULL/* FIXME */, 1); fz_var(dev); fz_try(ctx) { fz_clear_pixmap(ctx, glyph); dev = fz_new_draw_device_type3(ctx, NULL, glyph); fz_run_t3_glyph(ctx, font, gid, trm, dev); fz_close_device(ctx, dev); } fz_always(ctx) { fz_drop_device(ctx, dev); } fz_catch(ctx) { fz_drop_pixmap(ctx, glyph); fz_rethrow(ctx); } if (!model) { fz_try(ctx) { result = fz_alpha_from_gray(ctx, glyph); } fz_always(ctx) { fz_drop_pixmap(ctx, glyph); } fz_catch(ctx) { fz_rethrow(ctx); } } else result = glyph; return result; } fz_glyph * fz_render_t3_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, fz_colorspace *model, const fz_irect *scissor, int aa) { fz_pixmap *pixmap = fz_render_t3_glyph_pixmap(ctx, font, gid, trm, model, scissor, aa); return fz_new_glyph_from_pixmap(ctx, pixmap); } void fz_render_t3_glyph_direct(fz_context *ctx, fz_device *dev, fz_font *font, int gid, const fz_matrix *trm, void *gstate, int nested_depth, fz_default_colorspaces *def_cs) { fz_matrix ctm; void *contents; if (gid < 0 || gid > 255) return; contents = font->t3procs[gid]; if (!contents) return; if (font->t3flags[gid] & FZ_DEVFLAG_MASK) { if (font->t3flags[gid] & FZ_DEVFLAG_COLOR) fz_warn(ctx, "type3 glyph claims to be both masked and colored"); } else if (font->t3flags[gid] & FZ_DEVFLAG_COLOR) { } else { fz_warn(ctx, "type3 glyph doesn't specify masked or colored"); } fz_concat(&ctm, &font->t3matrix, trm); font->t3run(ctx, font->t3doc, font->t3resources, contents, dev, &ctm, gstate, nested_depth, def_cs); } fz_rect * fz_bound_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, fz_rect *rect) { if (font->bbox_table && gid < font->glyph_count) { if (fz_is_infinite_rect(&font->bbox_table[gid])) { if (font->ft_face) fz_bound_ft_glyph(ctx, font, gid); else if (font->t3lists) fz_bound_t3_glyph(ctx, font, gid); else font->bbox_table[gid] = fz_empty_rect; } *rect = font->bbox_table[gid]; if (fz_is_empty_rect(rect)) *rect = font->bbox; } else { /* fall back to font bbox */ *rect = font->bbox; } return fz_transform_rect(rect, trm); } fz_path * fz_outline_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *ctm) { if (!font->ft_face) return NULL; return fz_outline_ft_glyph(ctx, font, gid, ctm); } int fz_glyph_cacheable(fz_context *ctx, fz_font *font, int gid) { if (!font->t3procs || !font->t3flags || gid < 0 || gid >= font->glyph_count) return 1; return (font->t3flags[gid] & FZ_DEVFLAG_UNCACHEABLE) == 0; } static float fz_advance_ft_glyph(fz_context *ctx, fz_font *font, int gid, int wmode) { FT_Error fterr; FT_Fixed adv = 0; int mask; /* Substitute font widths. */ if (font->width_table) { if (gid < font->width_count) return font->width_table[gid] / 1000.0f; return font->width_default / 1000.0f; } mask = FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM; if (wmode) mask |= FT_LOAD_VERTICAL_LAYOUT; fz_lock(ctx, FZ_LOCK_FREETYPE); fterr = FT_Get_Advance(font->ft_face, gid, mask, &adv); fz_unlock(ctx, FZ_LOCK_FREETYPE); if (fterr) fz_warn(ctx, "freetype getting character advance: %s", ft_error_string(fterr)); return (float) adv / ((FT_Face)font->ft_face)->units_per_EM; } static float fz_advance_t3_glyph(fz_context *ctx, fz_font *font, int gid) { if (gid < 0 || gid > 255) return 0; return font->t3widths[gid]; } void fz_get_glyph_name(fz_context *ctx, fz_font *font, int glyph, char *buf, int size) { FT_Face face = font->ft_face; if (face) { if (FT_HAS_GLYPH_NAMES(face)) { int fterr = FT_Get_Glyph_Name(face, glyph, buf, size); if (fterr) fz_warn(ctx, "freetype get glyph name (gid %d): %s", glyph, ft_error_string(fterr)); } else fz_snprintf(buf, size, "%d", glyph); } else { fz_snprintf(buf, size, "%d", glyph); } } float fz_advance_glyph(fz_context *ctx, fz_font *font, int gid, int wmode) { if (font->ft_face) { if (wmode) return fz_advance_ft_glyph(ctx, font, gid, 1); if (gid >= 0 && gid < font->glyph_count && gid < MAX_ADVANCE_CACHE) { if (!font->advance_cache) { int i; font->advance_cache = fz_malloc_array(ctx, font->glyph_count, sizeof(float)); for (i = 0; i < font->glyph_count; ++i) font->advance_cache[i] = fz_advance_ft_glyph(ctx, font, i, 0); } return font->advance_cache[gid]; } return fz_advance_ft_glyph(ctx, font, gid, 0); } if (font->t3procs) return fz_advance_t3_glyph(ctx, font, gid); return 0; } int fz_encode_character(fz_context *ctx, fz_font *font, int ucs) { if (font->ft_face) { if (ucs >= 0 && ucs < 0x10000) { int pg = ucs >> 8; int ix = ucs & 0xFF; if (!font->encoding_cache[pg]) { int i; font->encoding_cache[pg] = fz_malloc_array(ctx, 256, sizeof(uint16_t)); for (i = 0; i < 256; ++i) font->encoding_cache[pg][i] = FT_Get_Char_Index(font->ft_face, (pg << 8) + i); } return font->encoding_cache[pg][ix]; } return FT_Get_Char_Index(font->ft_face, ucs); } return ucs; } /* FIXME: This should take language too eventually, to allow for fonts where we can select different * languages using opentype features. */ int fz_encode_character_with_fallback(fz_context *ctx, fz_font *user_font, int unicode, int script, int language, fz_font **out_font) { fz_font *font; int gid; gid = fz_encode_character(ctx, user_font, unicode); if (gid > 0) return *out_font = user_font, gid; if (script == 0) script = ucdn_get_script(unicode); /* Fix for ideographic/halfwidth/fullwidth punctuation forms. */ if ((unicode >= 0x3000 && unicode <= 0x303F) || (unicode >= 0xFF00 && unicode <= 0xFFEF)) { if (script != UCDN_SCRIPT_HANGUL && script != UCDN_SCRIPT_HIRAGANA && script != UCDN_SCRIPT_KATAKANA && script != UCDN_SCRIPT_BOPOMOFO) script = UCDN_SCRIPT_HAN; } font = fz_load_fallback_font(ctx, script, language, user_font->flags.is_serif, user_font->flags.is_bold, user_font->flags.is_italic); if (font) { gid = fz_encode_character(ctx, font, unicode); if (gid > 0) return *out_font = font, gid; } #ifndef TOFU_CJK_LANG if (script == UCDN_SCRIPT_HAN) { font = fz_load_fallback_font(ctx, script, FZ_LANG_zh_Hant, user_font->flags.is_serif, user_font->flags.is_bold, user_font->flags.is_italic); if (font) { gid = fz_encode_character(ctx, font, unicode); if (gid > 0) return *out_font = font, gid; } font = fz_load_fallback_font(ctx, script, FZ_LANG_ja, user_font->flags.is_serif, user_font->flags.is_bold, user_font->flags.is_italic); if (font) { gid = fz_encode_character(ctx, font, unicode); if (gid > 0) return *out_font = font, gid; } font = fz_load_fallback_font(ctx, script, FZ_LANG_ko, user_font->flags.is_serif, user_font->flags.is_bold, user_font->flags.is_italic); if (font) { gid = fz_encode_character(ctx, font, unicode); if (gid > 0) return *out_font = font, gid; } font = fz_load_fallback_font(ctx, script, FZ_LANG_zh_Hans, user_font->flags.is_serif, user_font->flags.is_bold, user_font->flags.is_italic); if (font) { gid = fz_encode_character(ctx, font, unicode); if (gid > 0) return *out_font = font, gid; } } #endif font = fz_load_fallback_symbol_font(ctx); if (font) { gid = fz_encode_character(ctx, font, unicode); if (gid > 0) return *out_font = font, gid; } font = fz_load_fallback_emoji_font(ctx); if (font) { gid = fz_encode_character(ctx, font, unicode); if (gid > 0) return *out_font = font, gid; } return *out_font = user_font, 0; } int fz_font_is_bold(fz_context *ctx, fz_font *font) { return font ? font->flags.is_bold : 0; } int fz_font_is_italic(fz_context *ctx, fz_font *font) { return font ? font->flags.is_italic : 0; } int fz_font_is_serif(fz_context *ctx, fz_font *font) { return font ? font->flags.is_serif : 0; } int fz_font_is_monospaced(fz_context *ctx, fz_font *font) { return font ? font->flags.is_mono : 0; } const char *fz_font_name(fz_context *ctx, fz_font *font) { return font ? font->name : ""; } fz_buffer **fz_font_t3_procs(fz_context *ctx, fz_font *font) { return font ? font->t3procs : NULL; } fz_rect *fz_font_bbox(fz_context *ctx, fz_font *font) { return font ? &font->bbox : NULL; } void *fz_font_ft_face(fz_context *ctx, fz_font *font) { return font ? font->ft_face : NULL; } fz_font_flags_t *fz_font_flags(fz_font *font) { return font ? &font->flags : NULL; } fz_shaper_data_t *fz_font_shaper_data(fz_context *ctx, fz_font *font) { return font ? &font->shaper_data : NULL; }