/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikafont.c * Copyright (C) 2003 Michael Natterer * Sven Neumann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #define PANGO_ENABLE_ENGINE 1 /* Argh */ #include #include #include FT_TRUETYPE_TABLES_H #include "libpikabase/pikabase.h" #include "libpikaconfig/pikaconfig.h" #include "text-types.h" #include "core/pikatempbuf.h" #include "core/pika-memsize.h" #include "core/pikacontainer.h" #include "pikafont.h" #include "pika-intl.h" /* This is a so-called pangram; it's supposed to contain all characters found in the alphabet. */ #define PIKA_TEXT_PANGRAM N_("Pack my box with\nfive dozen liquor jugs.") #define PIKA_FONT_POPUP_SIZE (PANGO_SCALE * 30) #define DEBUGPRINT(x) /* g_print x */ enum { PIKA_FONT_SYMBOL_FONTHASH, PIKA_FONT_SYMBOL_FULLNAME, PIKA_FONT_SYMBOL_FAMILY, PIKA_FONT_SYMBOL_STYLE, PIKA_FONT_SYMBOL_PSNAME, PIKA_FONT_SYMBOL_INDEX, PIKA_FONT_SYMBOL_WEIGHT, PIKA_FONT_SYMBOL_SLANT, PIKA_FONT_SYMBOL_WIDTH, PIKA_FONT_SYMBOL_FONTVERSION }; enum { PROP_0, PROP_PANGO_CONTEXT }; struct _PikaFont { PikaData parent_instance; PangoContext *pango_context; PangoLayout *popup_layout; gint popup_width; gint popup_height; gchar *lookup_name; /*properties for serialization*/ gchar *hash; gchar *fullname; gchar *family; gchar *style; gchar *psname; gint weight; gint width; gint index; gint slant; gint fontversion; /*for backward compatibility*/ gchar *desc; }; struct _PikaFontClass { PikaDataClass parent_class; PikaContainer *fontfactory; }; static void pika_font_finalize (GObject *object); static void pika_font_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_font_get_preview_size (PikaViewable *viewable, gint size, gboolean popup, gboolean dot_for_dot, gint *width, gint *height); static gboolean pika_font_get_popup_size (PikaViewable *viewable, gint width, gint height, gboolean dot_for_dot, gint *popup_width, gint *popup_height); static PikaTempBuf * pika_font_get_new_preview (PikaViewable *viewable, PikaContext *context, gint width, gint height); static void pika_font_config_iface_init (PikaConfigInterface *iface); static gboolean pika_font_serialize (PikaConfig *config, PikaConfigWriter *writer, gpointer data); static PikaConfig * pika_font_deserialize_create (GType type, GScanner *scanner, gint nest_level, gpointer data); static gint64 pika_font_get_memsize (PikaObject *object, gint64 *gui_size); static inline gboolean pika_font_covers_string (PangoFont *font, const gchar *sample); static hb_tag_t get_hb_table_type (PangoOTTableType table_type); static const gchar * pika_font_get_sample_string (PangoContext *context, PangoFontDescription *font_desc); static const gchar * pika_font_get_hash (PikaFont *font); G_DEFINE_TYPE_WITH_CODE (PikaFont, pika_font, PIKA_TYPE_DATA, G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONFIG, pika_font_config_iface_init)) #define parent_class pika_font_parent_class static void pika_font_config_iface_init (PikaConfigInterface *iface) { iface->serialize = pika_font_serialize; iface->deserialize_create = pika_font_deserialize_create; } static gboolean pika_font_serialize (PikaConfig *config, PikaConfigWriter *writer, gpointer data) { PikaFont *font; g_return_val_if_fail (PIKA_IS_FONT (config), FALSE); font = PIKA_FONT (config); if (font == PIKA_FONT (pika_font_get_standard ())) return TRUE; pika_config_writer_open (writer, "fonthash"); pika_config_writer_string (writer, pika_font_get_hash (font)); pika_config_writer_close (writer); pika_config_writer_open (writer, "fullname"); pika_config_writer_string (writer, font->fullname); pika_config_writer_close (writer); pika_config_writer_open (writer, "family"); pika_config_writer_string (writer, font->family); pika_config_writer_close (writer); pika_config_writer_open (writer, "style"); pika_config_writer_string (writer, font->style); pika_config_writer_close (writer); pika_config_writer_open (writer, "psname"); pika_config_writer_string (writer, font->psname); pika_config_writer_close (writer); pika_config_writer_open (writer, "index"); pika_config_writer_printf (writer, "%d", font->index); pika_config_writer_close (writer); pika_config_writer_open (writer, "weight"); pika_config_writer_printf (writer, "%d", font->weight); pika_config_writer_close (writer); pika_config_writer_open (writer, "slant"); pika_config_writer_printf (writer, "%d", font->slant); pika_config_writer_close (writer); pika_config_writer_open (writer, "width"); pika_config_writer_printf (writer, "%d", font->width); pika_config_writer_close (writer); pika_config_writer_open (writer, "fontversion"); pika_config_writer_printf (writer, "%d", font->fontversion); pika_config_writer_close (writer); return TRUE; } static PikaConfig * pika_font_deserialize_create (GType type, GScanner *scanner, gint nest_level, gpointer data) { PikaFont *font; PikaContainer *fonts_container = PIKA_FONT_CLASS (g_type_class_peek (PIKA_TYPE_FONT))->fontfactory; gint most_similar_font_index = -1; gint font_count = pika_container_get_n_children (fonts_container); gint largest_similarity = 0; GList *similar_fonts = NULL; GList *iter; gint i; gchar *fonthash = NULL; gchar *fullname = NULL; gchar *family = NULL; gchar *psname = NULL; gchar *style = NULL; gint index = -1; gint weight = -1; gint slant = -1; gint width = -1; gint fontversion = -1; guint scope_id; guint old_scope_id; /* This is for backward compatibility with older xcf files. * The font used to be serialized as a string containing * its name. */ if (g_scanner_peek_next_token (scanner) == G_TOKEN_STRING) { gchar* font_name = NULL; pika_scanner_parse_string (scanner, &font_name); for (i = 0; i < font_count; i++) { font = PIKA_FONT (pika_container_get_child_by_index (fonts_container, i)); if (!g_strcmp0 (font->desc, font_name)) break; font = NULL; } if (font == NULL) font = PIKA_FONT (pika_font_get_standard ()); else g_object_ref (font); g_free (font_name); return PIKA_CONFIG (PIKA_FONT (font)); } if (g_scanner_peek_next_token (scanner) == G_TOKEN_RIGHT_PAREN) return PIKA_CONFIG (PIKA_FONT (pika_font_get_standard ())); scope_id = g_type_qname (type); old_scope_id = g_scanner_set_scope (scanner, scope_id); g_scanner_scope_add_symbol (scanner, scope_id, "fonthash", GINT_TO_POINTER (PIKA_FONT_SYMBOL_FONTHASH)); g_scanner_scope_add_symbol (scanner, scope_id, "fullname", GINT_TO_POINTER (PIKA_FONT_SYMBOL_FULLNAME)); g_scanner_scope_add_symbol (scanner, scope_id, "family", GINT_TO_POINTER (PIKA_FONT_SYMBOL_FAMILY)); g_scanner_scope_add_symbol (scanner, scope_id, "style", GINT_TO_POINTER (PIKA_FONT_SYMBOL_STYLE)); g_scanner_scope_add_symbol (scanner, scope_id, "psname", GINT_TO_POINTER (PIKA_FONT_SYMBOL_PSNAME)); g_scanner_scope_add_symbol (scanner, scope_id, "index", GINT_TO_POINTER (PIKA_FONT_SYMBOL_INDEX)); g_scanner_scope_add_symbol (scanner, scope_id, "weight", GINT_TO_POINTER (PIKA_FONT_SYMBOL_WEIGHT)); g_scanner_scope_add_symbol (scanner, scope_id, "slant", GINT_TO_POINTER (PIKA_FONT_SYMBOL_SLANT)); g_scanner_scope_add_symbol (scanner, scope_id, "width", GINT_TO_POINTER (PIKA_FONT_SYMBOL_WIDTH)); g_scanner_scope_add_symbol (scanner, scope_id, "fontversion", GINT_TO_POINTER (PIKA_FONT_SYMBOL_FONTVERSION)); while (g_scanner_peek_next_token (scanner) == G_TOKEN_LEFT_PAREN) { GTokenType token; g_scanner_get_next_token (scanner); /* ( */ token = g_scanner_get_next_token (scanner); if (token != G_TOKEN_SYMBOL) { /* When encountering an unknown symbol, we simply ignore its contents * (up to the next closing parenthese). This would allow us to load * future XCF files in case we add new fields to recognize fonts * without having to bump the XCF version (just ignoring unknown * fields). We still output a small message on stderr. */ if (token == G_TOKEN_IDENTIFIER) g_printerr ("%s: ignoring unknown symbol '%s'.\n", G_STRFUNC, scanner->value.v_string); else g_printerr ("%s: ignoring unknown token %d.\n", G_STRFUNC, token); while ((token = g_scanner_get_next_token (scanner)) != G_TOKEN_EOF) { if (token == G_TOKEN_RIGHT_PAREN) break; } continue; } switch (GPOINTER_TO_INT (scanner->value.v_symbol)) { case PIKA_FONT_SYMBOL_FONTHASH: pika_scanner_parse_string (scanner, &fonthash); break; case PIKA_FONT_SYMBOL_FULLNAME: pika_scanner_parse_string (scanner, &fullname); break; case PIKA_FONT_SYMBOL_FAMILY: pika_scanner_parse_string (scanner, &family); break; case PIKA_FONT_SYMBOL_STYLE: pika_scanner_parse_string (scanner, &style); break; case PIKA_FONT_SYMBOL_PSNAME: pika_scanner_parse_string (scanner, &psname); break; case PIKA_FONT_SYMBOL_INDEX: pika_scanner_parse_int (scanner, &index); break; case PIKA_FONT_SYMBOL_WEIGHT: pika_scanner_parse_int (scanner, &weight); break; case PIKA_FONT_SYMBOL_SLANT: pika_scanner_parse_int (scanner, &slant); break; case PIKA_FONT_SYMBOL_WIDTH: pika_scanner_parse_int (scanner, &width); break; case PIKA_FONT_SYMBOL_FONTVERSION: pika_scanner_parse_int (scanner, &fontversion); break; default: break; } if (g_scanner_get_next_token (scanner) != G_TOKEN_RIGHT_PAREN) break; } g_scanner_scope_remove_symbol (scanner, scope_id, "fonthash"); g_scanner_scope_remove_symbol (scanner, scope_id, "fullname"); g_scanner_scope_remove_symbol (scanner, scope_id, "family"); g_scanner_scope_remove_symbol (scanner, scope_id, "style"); g_scanner_scope_remove_symbol (scanner, scope_id, "psname"); g_scanner_scope_remove_symbol (scanner, scope_id, "index"); g_scanner_scope_remove_symbol (scanner, scope_id, "weight"); g_scanner_scope_remove_symbol (scanner, scope_id, "slant"); g_scanner_scope_remove_symbol (scanner, scope_id, "width"); g_scanner_scope_remove_symbol (scanner, scope_id, "fontversion"); g_scanner_set_scope (scanner, old_scope_id); for (i = 0; i < font_count; i++) { gint current_font_similarity = 0; font = PIKA_FONT (pika_container_get_child_by_index (fonts_container, i)); /* Some attrs are more identifying than others, * hence their higher importance in measuring similarity. */ if (fullname != NULL && !g_strcmp0 (font->fullname, fullname)) current_font_similarity += 5; if (family != NULL && !g_strcmp0 (font->family, family)) current_font_similarity += 5; if (psname != NULL && font->psname != NULL && !g_strcmp0 (font->psname, psname)) current_font_similarity += 5; if (current_font_similarity < 5) continue; if (style != NULL && font->style != NULL && !g_strcmp0 (font->style, style)) current_font_similarity++; if (font->weight != -1 && font->weight == weight) current_font_similarity++; if (font->width != -1 && font->width == width) current_font_similarity++; if (font->slant != -1 && font->slant == slant) current_font_similarity++; if (font->index != -1 && font->index == index) current_font_similarity++; if (font->fontversion != -1 && font->fontversion == fontversion) current_font_similarity++; if (current_font_similarity > largest_similarity) { largest_similarity = current_font_similarity; most_similar_font_index = i; g_clear_pointer (&similar_fonts, g_list_free); similar_fonts = g_list_prepend (similar_fonts, GINT_TO_POINTER (i)); } else if (current_font_similarity == largest_similarity) { similar_fonts = g_list_prepend (similar_fonts, GINT_TO_POINTER (i)); } } /* In case there are multiple font with identical info, * the font file hash should be used for comparison. */ if (g_list_length (similar_fonts) == 1) g_clear_pointer (&similar_fonts, g_list_free); for (iter = similar_fonts; iter; iter = iter->next) { i = GPOINTER_TO_INT (iter->data); font = PIKA_FONT (pika_container_get_child_by_index (fonts_container, i)); if (g_strcmp0 (pika_font_get_hash (font), fonthash) == 0) { most_similar_font_index = i; break; } } if (most_similar_font_index > -1) { font = PIKA_FONT (pika_container_get_child_by_index (fonts_container, most_similar_font_index)); g_object_ref (font); } else { font = PIKA_FONT (pika_font_get_standard ()); } g_list_free (similar_fonts); g_free (fonthash); g_free (fullname); g_free (family); g_free (psname); g_free (style); return PIKA_CONFIG (font); } void pika_font_class_set_font_factory (PikaContainer *factory) { PikaFontClass *klass = PIKA_FONT_CLASS (g_type_class_peek (PIKA_TYPE_FONT)); klass->fontfactory = factory; } void pika_font_set_font_info (PikaFont *font, gpointer font_info[]) { font->fullname = g_strdup ((gchar*)font_info[PROP_FULLNAME]); font->family = g_strdup ((gchar*)font_info[PROP_FAMILY]); font->style = g_strdup ((gchar*)font_info[PROP_STYLE]); font->psname = g_strdup ((gchar*)font_info[PROP_PSNAME]); font->desc = g_strdup ((gchar*)font_info[PROP_DESC]); font->weight = *(gint*)font_info[PROP_WEIGHT]; font->width = *(gint*)font_info[PROP_WIDTH]; font->index = *(gint*)font_info[PROP_INDEX]; font->slant = *(gint*)font_info[PROP_SLANT]; font->fontversion = *(gint*)font_info[PROP_FONTVERSION]; } void pika_font_set_lookup_name (PikaFont *font, gchar *name) { font->lookup_name = name; } gboolean pika_font_match_by_lookup_name (PikaFont *font, const gchar *name) { return !g_strcmp0 (font->lookup_name, name); } const gchar* pika_font_get_lookup_name (PikaFont *font) { return font->lookup_name; } static void pika_font_class_init (PikaFontClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); object_class->finalize = pika_font_finalize; object_class->set_property = pika_font_set_property; pika_object_class->get_memsize = pika_font_get_memsize; viewable_class->get_preview_size = pika_font_get_preview_size; viewable_class->get_popup_size = pika_font_get_popup_size; viewable_class->get_new_preview = pika_font_get_new_preview; viewable_class->default_icon_name = "gtk-select-font"; g_object_class_install_property (object_class, PROP_PANGO_CONTEXT, g_param_spec_object ("pango-context", NULL, NULL, PANGO_TYPE_CONTEXT, PIKA_PARAM_WRITABLE)); } static void pika_font_init (PikaFont *font) { } static void pika_font_finalize (GObject *object) { PikaFont *font = PIKA_FONT (object); g_clear_object (&font->pango_context); g_clear_object (&font->popup_layout); g_free (font->lookup_name); g_free (font->hash); g_free (font->fullname); g_free (font->family); g_free (font->style); g_free (font->psname); g_free (font->desc); G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_font_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaFont *font = PIKA_FONT (object); switch (property_id) { case PROP_PANGO_CONTEXT: if (font->pango_context) g_object_unref (font->pango_context); font->pango_context = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static gint64 pika_font_get_memsize (PikaObject *object, gint64 *gui_size) { PikaFont *font = PIKA_FONT (object); gint64 memsize = 0; memsize += pika_string_get_memsize (font->lookup_name); memsize += pika_string_get_memsize (font->hash); memsize += pika_string_get_memsize (font->fullname); memsize += pika_string_get_memsize (font->family); memsize += pika_string_get_memsize (font->style); memsize += pika_string_get_memsize (font->psname); memsize += pika_string_get_memsize (font->desc); return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static void pika_font_get_preview_size (PikaViewable *viewable, gint size, gboolean popup, gboolean dot_for_dot, gint *width, gint *height) { *width = size; *height = size; } static gboolean pika_font_get_popup_size (PikaViewable *viewable, gint width, gint height, gboolean dot_for_dot, gint *popup_width, gint *popup_height) { PikaFont *font = PIKA_FONT (viewable); PangoFontDescription *font_desc; PangoRectangle ink; PangoRectangle logical; const gchar *name; if (! font->pango_context) return FALSE; name = font->lookup_name; font_desc = pango_font_description_from_string (name); g_return_val_if_fail (font_desc != NULL, FALSE); pango_font_description_set_size (font_desc, PIKA_FONT_POPUP_SIZE); if (font->popup_layout) g_object_unref (font->popup_layout); font->popup_layout = pango_layout_new (font->pango_context); pango_layout_set_font_description (font->popup_layout, font_desc); pango_font_description_free (font_desc); pango_layout_set_text (font->popup_layout, gettext (PIKA_TEXT_PANGRAM), -1); pango_layout_get_pixel_extents (font->popup_layout, &ink, &logical); *popup_width = MAX (ink.width, logical.width) + 6; *popup_height = MAX (ink.height, logical.height) + 6; *popup_width = cairo_format_stride_for_width (CAIRO_FORMAT_A8, *popup_width); font->popup_width = *popup_width; font->popup_height = *popup_height; return TRUE; } static PikaTempBuf * pika_font_get_new_preview (PikaViewable *viewable, PikaContext *context, gint width, gint height) { PikaFont *font = PIKA_FONT (viewable); PangoLayout *layout; PangoRectangle ink; PangoRectangle logical; gint layout_width; gint layout_height; gint layout_x; gint layout_y; PikaTempBuf *temp_buf; cairo_t *cr; cairo_surface_t *surface; if (! font->pango_context) return NULL; if (! font->popup_layout || font->popup_width != width || font->popup_height != height) { PangoFontDescription *font_desc; const gchar *name; name = font->lookup_name; DEBUGPRINT (("%s: ", name)); font_desc = pango_font_description_from_string (name); g_return_val_if_fail (font_desc != NULL, NULL); pango_font_description_set_size (font_desc, PANGO_SCALE * height * 2.0 / 3.0); layout = pango_layout_new (font->pango_context); pango_layout_set_font_description (layout, font_desc); pango_layout_set_text (layout, pika_font_get_sample_string (font->pango_context, font_desc), -1); pango_font_description_free (font_desc); } else { layout = g_object_ref (font->popup_layout); } width = cairo_format_stride_for_width (CAIRO_FORMAT_A8, width); temp_buf = pika_temp_buf_new (width, height, babl_format ("Y' u8")); memset (pika_temp_buf_get_data (temp_buf), 255, width * height); surface = cairo_image_surface_create_for_data (pika_temp_buf_get_data (temp_buf), CAIRO_FORMAT_A8, width, height, width); pango_layout_get_pixel_extents (layout, &ink, &logical); layout_width = MAX (ink.width, logical.width); layout_height = MAX (ink.height, logical.height); layout_x = (width - layout_width) / 2; layout_y = (height - layout_height) / 2; if (ink.x < logical.x) layout_x += logical.x - ink.x; if (ink.y < logical.y) layout_y += logical.y - ink.y; cr = cairo_create (surface); cairo_translate (cr, layout_x, layout_y); cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); pango_cairo_show_layout (cr, layout); cairo_destroy (cr); cairo_surface_destroy (surface); g_object_unref (layout); return temp_buf; } PikaData * pika_font_get_standard (void) { static PikaData *standard_font = NULL; if (! standard_font) { g_set_weak_pointer (&standard_font, g_object_new (PIKA_TYPE_FONT, "name", "", NULL));; /* pango_font_description_from_string doesn't accept NULL */ PIKA_FONT (standard_font)->lookup_name = g_strdup (""); pika_data_clean (standard_font); pika_data_make_internal (standard_font, "pika-font-standard"); } return standard_font; } /* Private functions */ static inline gboolean pika_font_covers_string (PangoFont *font, const gchar *sample) { const gchar *p; for (p = sample; *p; p = g_utf8_next_char (p)) { if (! pango_font_has_char (font, g_utf8_get_char (p))) return FALSE; } return TRUE; } /* This function was picked up from Pango's pango-ot-info.c. Until there * is a better way to get the tag, we use this. */ static hb_tag_t get_hb_table_type (PangoOTTableType table_type) { switch (table_type) { case PANGO_OT_TABLE_GSUB: return HB_OT_TAG_GSUB; case PANGO_OT_TABLE_GPOS: return HB_OT_TAG_GPOS; default: return HB_TAG_NONE; } } /* Guess a suitable short sample string for the font. */ static const gchar * pika_font_get_sample_string (PangoContext *context, PangoFontDescription *font_desc) { PangoFont *font; hb_face_t *hb_face; hb_font_t *hb_font; FT_Face face; TT_OS2 *os2; PangoOTTableType tt; gint i; /* This is a table of scripts and corresponding short sample strings * to be used instead of the Latin sample string Aa. The script * codes are as in ISO15924 (see * https://www.unicode.org/iso15924/iso15924-codes.html), but in * lower case. The Unicode subrange bit numbers, as used in TrueType * so-called OS/2 tables, are from * https://www.microsoft.com/typography/otspec/os2.htm#ur . * * The table is mostly ordered by Unicode order. But as there are * fonts that support several of these scripts, the ordering is * be modified so that the script which such a font is more likely * to be actually designed for comes first and matches. * * These sample strings are mostly just guesswork as for their * usefulness. Usually they contain what I assume is the first * letter in the corresponding alphabet, or two first letters if the * first one happens to look too "trivial" to be recognizable by * itself. * * This table is used to determine the primary script a font has * been designed for. * * Very useful link: https://www.wazu.jp/index.html */ static const struct { const gchar script[4]; gint bit; const gchar *sample; } scripts[] = { /* CJK are first because fonts that support these characters * presumably are primarily designed for it. * * Though not a universal rules, most fonts targeting Korean will * also have the ideographs as they were used historically (and * still in some cases). As for Japanese, ideographs are still * definitely used daily. After all the block is called "*CJK* * (Chinese-Japanese-Korean) Unified Ideographs". So let's give the * Chinese representation less priority towards finding * Korean/Japanese targeting. * Then we prioritize Korean because many fonts for Korean also * design Hiragana/Katakana blocks. On the other hand, I could see * very few (none so far) Japanese fonts designing also Hangul * characters. So if we see both Hiragana and Hangul, we can assume * this is a font for Korean market, whereas Hiragana only is for * Japanese. * XXX: of course we could probably come with a better algorithm to * determine the target language. Probably looking at more than the * existence of a single block would help (since some languages are * spread on several blocks). */ { "hang", /* Korean alphabet (Hangul) */ 56, /* Hangul Syllables block */ "\355\225\234" /* U+D55C HANGUL SYLLABLE HAN */ }, { "japa", /* Japanese */ 49, /* Hiragana block */ "\343\201\202" /* U+3042 HIRAGANA LETTER A */ }, { "hani", /* Han Ideographic */ 59, /* CJK Unified Ideographs */ "\346\260\270" /* U+6C38 "forever". Ed Trager says * this is a "pan-stroke" often used * in teaching Chinese calligraphy. It * contains the eight basic Chinese * stroke forms. */ }, { "copt", /* Coptic */ 7, "\316\221\316\261" /* U+0410 GREEK CAPITAL LETTER ALPHA U+0430 GREEK SMALL LETTER ALPHA */ }, { "grek", /* Greek */ 7, "\316\221\316\261" /* U+0410 GREEK CAPITAL LETTER ALPHA U+0430 GREEK SMALL LETTER ALPHA */ }, { "cyrl", /* Cyrillic */ 9, "\320\220\325\260" /* U+0410 CYRILLIC CAPITAL LETTER A U+0430 CYRILLIC SMALL LETTER A */ }, { "armn", /* Armenian */ 10, "\324\261\325\241" /* U+0531 ARMENIAN CAPITAL LETTER AYB U+0561 ARMENIAN SMALL LETTER AYB */ }, { "hebr", /* Hebrew */ 11, "\327\220" /* U+05D0 HEBREW LETTER ALEF */ }, { "arab", /* Arabic */ 13, "\330\247\330\250" /* U+0627 ARABIC LETTER ALEF * U+0628 ARABIC LETTER BEH */ }, { "syrc", /* Syriac */ 71, "\334\220\334\222" /* U+0710 SYRIAC LETTER ALAPH * U+0712 SYRIAC LETTER BETH */ }, { "thaa", /* Thaana */ 72, "\336\200\336\201" /* U+0780 THAANA LETTER HAA * U+0781 THAANA LETTER SHAVIYANI */ }, /* Should really use some better sample strings for the complex * scripts. Something that shows how well the font handles the * complex processing for each script. */ { "deva", /* Devanagari */ 15, "\340\244\205" /* U+0905 DEVANAGARI LETTER A*/ }, { "beng", /* Bengali */ 16, "\340\246\205" /* U+0985 BENGALI LETTER A */ }, { "guru", /* Gurmukhi */ 17, "\340\250\205" /* U+0A05 GURMUKHI LETTER A */ }, { "gujr", /* Gujarati */ 18, "\340\252\205" /* U+0A85 GUJARATI LETTER A */ }, { "orya", /* Oriya */ 19, "\340\254\205" /* U+0B05 ORIYA LETTER A */ }, { "taml", /* Tamil */ 20, "\340\256\205" /* U+0B85 TAMIL LETTER A */ }, { "telu", /* Telugu */ 21, "\340\260\205" /* U+0C05 TELUGU LETTER A */ }, { "knda", /* Kannada */ 22, "\340\262\205" /* U+0C85 KANNADA LETTER A */ }, { "mylm", /* Malayalam */ 23, "\340\264\205" /* U+0D05 MALAYALAM LETTER A */ }, { "sinh", /* Sinhala */ 73, "\340\266\205" /* U+0D85 SINHALA LETTER AYANNA */ }, { "thai", /* Thai */ 24, "\340\270\201\340\270\264"/* U+0E01 THAI CHARACTER KO KAI * U+0E34 THAI CHARACTER SARA I */ }, { "laoo", /* Lao */ 25, "\340\272\201\340\272\264"/* U+0E81 LAO LETTER KO * U+0EB4 LAO VOWEL SIGN I */ }, { "tibt", /* Tibetan */ 70, "\340\274\200" /* U+0F00 TIBETAN SYLLABLE OM */ }, { "mymr", /* Myanmar */ 74, "\341\200\200" /* U+1000 MYANMAR LETTER KA */ }, { "geor", /* Georgian */ 26, "\341\202\240\341\203\200" /* U+10A0 GEORGIAN CAPITAL LETTER AN * U+10D0 GEORGIAN LETTER AN */ }, { "ethi", /* Ethiopic */ 75, "\341\210\200" /* U+1200 ETHIOPIC SYLLABLE HA */ }, { "cher", /* Cherokee */ 76, "\341\216\243" /* U+13A3 CHEROKEE LETTER O */ }, { "cans", /* Unified Canadian Aboriginal Syllabics */ 77, "\341\220\201" /* U+1401 CANADIAN SYLLABICS E */ }, { "ogam", /* Ogham */ 78, "\341\232\201" /* U+1681 OGHAM LETTER BEITH */ }, { "runr", /* Runic */ 79, "\341\232\240" /* U+16A0 RUNIC LETTER FEHU FEOH FE F */ }, { "tglg", /* Tagalog */ 84, "\341\234\200" /* U+1700 TAGALOG LETTER A */ }, { "hano", /* Hanunoo */ -1, "\341\234\240" /* U+1720 HANUNOO LETTER A */ }, { "buhd", /* Buhid */ -1, "\341\235\200" /* U+1740 BUHID LETTER A */ }, { "tagb", /* Tagbanwa */ -1, "\341\235\240" /* U+1760 TAGBANWA LETTER A */ }, { "khmr", /* Khmer */ 80, "\341\236\201\341\237\222\341\236\211\341\236\273\341\237\206" /* U+1781 KHMER LETTER KHA * U+17D2 KHMER LETTER SIGN COENG * U+1789 KHMER LETTER NYO * U+17BB KHMER VOWEL SIGN U * U+17C6 KHMER SIGN NIKAHIT * A common word meaning "I" that contains * lots of complex processing. */ }, { "mong", /* Mongolian */ 81, "\341\240\240" /* U+1820 MONGOLIAN LETTER A */ }, { "limb", /* Limbu */ -1, "\341\244\201" /* U+1901 LIMBU LETTER KA */ }, { "tale", /* Tai Le */ -1, "\341\245\220" /* U+1950 TAI LE LETTER KA */ }, { "latn", /* Latin */ 0, "Aa" } }; gint ot_alts[4]; gint n_ot_alts = 0; gint sr_alts[20]; gint n_sr_alts = 0; font = pango_context_load_font (context, font_desc); g_return_val_if_fail (PANGO_IS_FC_FONT (font), "Aa"); hb_font = pango_font_get_hb_font (font); g_return_val_if_fail (hb_font != NULL, "Aa"); /* These are needed to set hb_font to the right internal format, which * can only work if the font is not immutable. Hence we make a copy. */ hb_font = hb_font_create_sub_font (hb_font); hb_ft_font_set_funcs (hb_font); /* TODO: use hb_ft_font_lock_face/hb_ft_font_unlock_face() when we * bump to harfbuzz >= 2.6.5. */ face = hb_ft_font_get_face (hb_font); /* Are there actual cases where this function could return NULL while * it's not a bug in the code? * For instance if the font file is broken, we don't want to return a * CRITICAL, but properly address the issue (removing the font with a * warning or whatnot). * See #5922. */ g_return_val_if_fail (face != NULL, "Aa"); hb_face = hb_ft_face_create (face, NULL); /* First check what script(s), if any, the font has GSUB or GPOS * OpenType layout tables for. */ for (tt = PANGO_OT_TABLE_GSUB; n_ot_alts < G_N_ELEMENTS (ot_alts) && tt <= PANGO_OT_TABLE_GPOS; tt++) { hb_tag_t tag; unsigned int count; PangoOTTag *slist; tag = get_hb_table_type (tt); count = hb_ot_layout_table_get_script_tags (hb_face, tag, 0, NULL, NULL); slist = g_new (PangoOTTag, count + 1); hb_ot_layout_table_get_script_tags (hb_face, tag, 0, &count, slist); slist[count] = 0; for (i = 0; n_ot_alts < G_N_ELEMENTS (ot_alts) && i < G_N_ELEMENTS (scripts); i++) { gint j, k; for (k = 0; k < n_ot_alts; k++) if (ot_alts[k] == i) break; if (k == n_ot_alts) { for (j = 0; n_ot_alts < G_N_ELEMENTS (ot_alts) && slist[j]; j++) { #define TAG(s) FT_MAKE_TAG (s[0], s[1], s[2], s[3]) if (slist[j] == TAG (scripts[i].script) && pika_font_covers_string (font, scripts[i].sample)) { ot_alts[n_ot_alts++] = i; DEBUGPRINT (("%.4s ", scripts[i].script)); } #undef TAG } } } g_free (slist); } hb_face_destroy (hb_face); DEBUGPRINT (("; OS/2: ")); /* Next check the OS/2 table for Unicode ranges the font claims * to cover. */ os2 = FT_Get_Sfnt_Table (face, ft_sfnt_os2); if (os2) { for (i = 0; n_sr_alts < G_N_ELEMENTS (sr_alts) && i < G_N_ELEMENTS (scripts); i++) { if (scripts[i].bit >= 0 && (&os2->ulUnicodeRange1)[scripts[i].bit/32] & (1 << (scripts[i].bit % 32)) && pika_font_covers_string (font, scripts[i].sample)) { sr_alts[n_sr_alts++] = i; DEBUGPRINT (("%.4s ", scripts[i].script)); } } } hb_font_destroy (hb_font); g_object_unref (font); if (n_ot_alts > 2) { /* The font has OpenType tables for several scripts. If it * support Basic Latin as well, use Aa. */ gint i; for (i = 0; i < n_sr_alts; i++) if (scripts[sr_alts[i]].bit == 0) { DEBUGPRINT (("=> several OT, also latin, use Aa\n")); return "Aa"; } } if (n_ot_alts > 0 && n_sr_alts >= n_ot_alts + 3) { /* At least one script with an OpenType table, but many more * subranges than such scripts. If it supports Basic Latin, * use Aa, else the highest priority subrange. */ gint i; for (i = 0; i < n_sr_alts; i++) if (scripts[sr_alts[i]].bit == 0) { DEBUGPRINT (("=> several SR, also latin, use Aa\n")); return "Aa"; } DEBUGPRINT (("=> several SR, use %.4s\n", scripts[sr_alts[0]].script)); return scripts[sr_alts[0]].sample; } if (n_ot_alts > 0) { /* OpenType tables for at least one script, use the * highest priority one */ DEBUGPRINT (("=> at least one OT, use %.4s\n", scripts[sr_alts[0]].script)); return scripts[ot_alts[0]].sample; } if (n_sr_alts > 0) { /* Use the highest priority subrange. This means that a * font that supports Greek, Cyrillic and Latin (quite * common), will get the Greek sample string. That is * capital and lowercase alpha, which looks like capital A * and lowercase alpha, so it's actually quite nice, and * doesn't give a too strong impression that the font would * be for Greek only. */ DEBUGPRINT (("=> at least one SR, use %.4s\n", scripts[sr_alts[0]].script)); return scripts[sr_alts[0]].sample; } /* Final fallback */ DEBUGPRINT (("=> fallback, use Aa\n")); return "Aa"; } static const gchar * pika_font_get_hash (PikaFont *font) { /* Computing the hash is expensive, so it's only done in a few case, such as * when serializing, when a similar fonts at deserialization looks like it * could be a good identity candidate. */ if (font->hash == NULL) { PangoFontDescription *pfd = pango_font_description_from_string (font->lookup_name); PangoFcFont *pango_font = PANGO_FC_FONT (pango_context_load_font (font->pango_context, pfd)); GChecksum *checksum = g_checksum_new (G_CHECKSUM_SHA256); gchar *file; hb_blob_t *hb_blob; guint length; const char *hb_data; FcPatternGetString (pango_fc_font_get_pattern (pango_font), FC_FILE, 0, (FcChar8 **) &file); hb_blob = hb_blob_create_from_file (file); hb_data = hb_blob_get_data (hb_blob, &length); g_checksum_update (checksum, (const guchar*) hb_data, length); font->hash = g_strdup (g_checksum_get_string (checksum)); pango_font_description_free (pfd); g_object_unref (pango_font); hb_blob_destroy (hb_blob); g_checksum_free (checksum); } return font->hash; }