diff options
Diffstat (limited to 'app/text/gimpfont.c')
-rw-r--r-- | app/text/gimpfont.c | 820 |
1 files changed, 820 insertions, 0 deletions
diff --git a/app/text/gimpfont.c b/app/text/gimpfont.c new file mode 100644 index 0000000..148275d --- /dev/null +++ b/app/text/gimpfont.c @@ -0,0 +1,820 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfont.c + * Copyright (C) 2003 Michael Natterer <mitch@gimp.org> + * Sven Neumann <sven@gimp.org> + * + * 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 <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gegl.h> + +#include <pango/pangocairo.h> + +#include <hb.h> +#include <hb-ot.h> +#include <hb-ft.h> + +#define PANGO_ENABLE_ENGINE 1 /* Argh */ +#include <pango/pango-ot.h> + +#include <ft2build.h> +#include FT_TRUETYPE_TABLES_H + +#include "text-types.h" + +#include "core/gimptempbuf.h" + +#include "gimpfont.h" + +#include "gimp-intl.h" + + +/* This is a so-called pangram; it's supposed to + contain all characters found in the alphabet. */ +#define GIMP_TEXT_PANGRAM N_("Pack my box with\nfive dozen liquor jugs.") + +#define GIMP_FONT_POPUP_SIZE (PANGO_SCALE * 30) + +#define DEBUGPRINT(x) /* g_print x */ + +enum +{ + PROP_0, + PROP_PANGO_CONTEXT +}; + + +struct _GimpFont +{ + GimpData parent_instance; + + PangoContext *pango_context; + + PangoLayout *popup_layout; + gint popup_width; + gint popup_height; +}; + +struct _GimpFontClass +{ + GimpDataClass parent_class; +}; + + +static void gimp_font_constructed (GObject *object); +static void gimp_font_finalize (GObject *object); +static void gimp_font_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_font_get_preview_size (GimpViewable *viewable, + gint size, + gboolean popup, + gboolean dot_for_dot, + gint *width, + gint *height); +static gboolean gimp_font_get_popup_size (GimpViewable *viewable, + gint width, + gint height, + gboolean dot_for_dot, + gint *popup_width, + gint *popup_height); +static GimpTempBuf * gimp_font_get_new_preview (GimpViewable *viewable, + GimpContext *context, + gint width, + gint height); + +static const gchar * gimp_font_get_sample_string (PangoContext *context, + PangoFontDescription *font_desc); + + +G_DEFINE_TYPE (GimpFont, gimp_font, GIMP_TYPE_DATA) + +#define parent_class gimp_font_parent_class + + +static void +gimp_font_class_init (GimpFontClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass); + + object_class->constructed = gimp_font_constructed; + object_class->finalize = gimp_font_finalize; + object_class->set_property = gimp_font_set_property; + + viewable_class->get_preview_size = gimp_font_get_preview_size; + viewable_class->get_popup_size = gimp_font_get_popup_size; + viewable_class->get_new_preview = gimp_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, + GIMP_PARAM_WRITABLE)); +} + +static void +gimp_font_init (GimpFont *font) +{ +} + +static void +gimp_font_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_data_make_internal (GIMP_DATA (object), + gimp_object_get_name (object)); +} + +static void +gimp_font_finalize (GObject *object) +{ + GimpFont *font = GIMP_FONT (object); + + g_clear_object (&font->pango_context); + g_clear_object (&font->popup_layout); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_font_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpFont *font = GIMP_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 void +gimp_font_get_preview_size (GimpViewable *viewable, + gint size, + gboolean popup, + gboolean dot_for_dot, + gint *width, + gint *height) +{ + *width = size; + *height = size; +} + +static gboolean +gimp_font_get_popup_size (GimpViewable *viewable, + gint width, + gint height, + gboolean dot_for_dot, + gint *popup_width, + gint *popup_height) +{ + GimpFont *font = GIMP_FONT (viewable); + PangoFontDescription *font_desc; + PangoRectangle ink; + PangoRectangle logical; + const gchar *name; + + if (! font->pango_context) + return FALSE; + + name = gimp_object_get_name (font); + + font_desc = pango_font_description_from_string (name); + g_return_val_if_fail (font_desc != NULL, FALSE); + + pango_font_description_set_size (font_desc, GIMP_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 (GIMP_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 GimpTempBuf * +gimp_font_get_new_preview (GimpViewable *viewable, + GimpContext *context, + gint width, + gint height) +{ + GimpFont *font = GIMP_FONT (viewable); + PangoLayout *layout; + PangoRectangle ink; + PangoRectangle logical; + gint layout_width; + gint layout_height; + gint layout_x; + gint layout_y; + GimpTempBuf *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 = gimp_object_get_name (font); + + 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, + gimp_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 = gimp_temp_buf_new (width, height, babl_format ("Y' u8")); + memset (gimp_temp_buf_get_data (temp_buf), 255, width * height); + + surface = cairo_image_surface_create_for_data (gimp_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; +} + +GimpData * +gimp_font_get_standard (void) +{ + static GimpData *standard_font = NULL; + + if (! standard_font) + { + standard_font = g_object_new (GIMP_TYPE_FONT, + "name", "", + NULL); + + gimp_data_clean (standard_font); + gimp_data_make_internal (standard_font, "gimp-font-standard"); + + g_object_add_weak_pointer (G_OBJECT (standard_font), + (gpointer *) &standard_font); + } + + return standard_font; +} + + +static inline gboolean +gimp_font_covers_string (PangoFcFont *font, + const gchar *sample) +{ + const gchar *p; + + for (p = sample; *p; p = g_utf8_next_char (p)) + { + if (! pango_fc_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 * +gimp_font_get_sample_string (PangoContext *context, + PangoFontDescription *font_desc) +{ + PangoFont *font; + hb_face_t *hb_face; + 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[] = { + /* Han is first because fonts that support it presumably are primarily + * designed for it. + */ + { + "hani", /* Han Ideographic */ + 59, + "\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 + */ + }, + { + "hang", /* Hangul */ + 28, + "\341\204\200\341\204\201"/* U+1100 HANGUL CHOSEONG KIYEOK + * U+1101 HANGUL CHOSEONG SSANGKIYEOK + */ + }, + { + "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"); + + face = pango_fc_font_lock_face (PANGO_FC_FONT (font)); + 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) && + gimp_font_covers_string (PANGO_FC_FONT (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)) && + gimp_font_covers_string (PANGO_FC_FONT (font), + scripts[i].sample)) + { + sr_alts[n_sr_alts++] = i; + DEBUGPRINT (("%.4s ", scripts[i].script)); + } + } + } + + pango_fc_font_unlock_face (PANGO_FC_FONT (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"; +} |