/* 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 * * pikafontfactory.c * Copyright (C) 2003-2018 Michael Natterer * * Partly based on code Copyright (C) Sven Neumann * Manish Singh * * 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 "libpikabase/pikabase.h" #include "libpikaconfig/pikaconfig.h" #include "text-types.h" #include "core/pika.h" #include "core/pika-parallel.h" #include "core/pikaasync.h" #include "core/pikaasyncset.h" #include "core/pikacancelable.h" #include "core/pikacontainer.h" #include "pikafont.h" #include "pikafontfactory.h" #include "pika-intl.h" #include #define CONF_FNAME "fonts.conf" struct _PikaFontFactoryPrivate { gpointer foo; /* can't have an empty struct */ }; #define GET_PRIVATE(obj) (((PikaFontFactory *) (obj))->priv) static void pika_font_factory_data_init (PikaDataFactory *factory, PikaContext *context); static void pika_font_factory_data_refresh (PikaDataFactory *factory, PikaContext *context); static void pika_font_factory_data_save (PikaDataFactory *factory); static void pika_font_factory_data_cancel (PikaDataFactory *factory); static PikaData * pika_font_factory_data_duplicate (PikaDataFactory *factory, PikaData *data); static gboolean pika_font_factory_data_delete (PikaDataFactory *factory, PikaData *data, gboolean delete_from_disk, GError **error); static void pika_font_factory_load (PikaFontFactory *factory, GError **error); static gboolean pika_font_factory_load_fonts_conf (FcConfig *config, GFile *fonts_conf); static void pika_font_factory_add_directories (FcConfig *config, GList *path, GError **error); static void pika_font_factory_recursive_add_fontdir (FcConfig *config, GFile *file, GError **error); static void pika_font_factory_load_names (PikaContainer *container, PangoFontMap *fontmap, PangoContext *context); G_DEFINE_TYPE_WITH_PRIVATE (PikaFontFactory, pika_font_factory, PIKA_TYPE_DATA_FACTORY) #define parent_class pika_font_factory_parent_class static void pika_font_factory_class_init (PikaFontFactoryClass *klass) { PikaDataFactoryClass *factory_class = PIKA_DATA_FACTORY_CLASS (klass); factory_class->data_init = pika_font_factory_data_init; factory_class->data_refresh = pika_font_factory_data_refresh; factory_class->data_save = pika_font_factory_data_save; factory_class->data_cancel = pika_font_factory_data_cancel; factory_class->data_duplicate = pika_font_factory_data_duplicate; factory_class->data_delete = pika_font_factory_data_delete; } static void pika_font_factory_init (PikaFontFactory *factory) { factory->priv = pika_font_factory_get_instance_private (factory); } static void pika_font_factory_data_init (PikaDataFactory *factory, PikaContext *context) { GError *error = NULL; pika_font_factory_load (PIKA_FONT_FACTORY (factory), &error); if (error) { pika_message_literal (pika_data_factory_get_pika (factory), NULL, PIKA_MESSAGE_INFO, error->message); g_error_free (error); } } static void pika_font_factory_data_refresh (PikaDataFactory *factory, PikaContext *context) { GError *error = NULL; pika_font_factory_load (PIKA_FONT_FACTORY (factory), &error); if (error) { pika_message_literal (pika_data_factory_get_pika (factory), NULL, PIKA_MESSAGE_INFO, error->message); g_error_free (error); } } static void pika_font_factory_data_save (PikaDataFactory *factory) { /* this is not "saving" but this functions is called at the right * time at exit to reset the config */ /* if font loading is in progress in another thread, do nothing. calling * FcInitReinitialize() while loading takes place is unsafe. */ if (! pika_async_set_is_empty (pika_data_factory_get_async_set (factory))) return; /* Reinit the library with defaults. */ FcInitReinitialize (); } static void pika_font_factory_data_cancel (PikaDataFactory *factory) { PikaAsyncSet *async_set = pika_data_factory_get_async_set (factory); /* we can't really cancel font loading, so we just clear the async set and * return without waiting for loading to finish. we also cancel the async * set beforehand, as a way to signal to * pika_font_factory_load_async_callback() that loading was canceled and the * factory might be dead, and that it should just do nothing. */ pika_cancelable_cancel (PIKA_CANCELABLE (async_set)); pika_async_set_clear (async_set); } static PikaData * pika_font_factory_data_duplicate (PikaDataFactory *factory, PikaData *data) { return NULL; } static gboolean pika_font_factory_data_delete (PikaDataFactory *factory, PikaData *data, gboolean delete_from_disk, GError **error) { return TRUE; } /* public functions */ PikaDataFactory * pika_font_factory_new (Pika *pika, const gchar *path_property_name) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (path_property_name != NULL, NULL); return g_object_new (PIKA_TYPE_FONT_FACTORY, "pika", pika, "data-type", PIKA_TYPE_FONT, "path-property-name", path_property_name, "get-standard-func", pika_font_get_standard, "unique-names", FALSE, NULL); } /* private functions */ static void pika_font_factory_load_async (PikaAsync *async, FcConfig *config) { if (FcConfigBuildFonts (config)) { pika_async_finish (async, config); } else { FcConfigDestroy (config); pika_async_abort (async); } } static void pika_font_factory_load_async_callback (PikaAsync *async, PikaFontFactory *factory) { PikaContainer *container; /* the operation was canceled and the factory might be dead (see * pika_font_factory_data_cancel()). bail. */ if (pika_async_is_canceled (async)) return; container = pika_data_factory_get_container (PIKA_DATA_FACTORY (factory)); if (pika_async_is_finished (async)) { FcConfig *config = pika_async_get_result (async); PangoFontMap *fontmap; PangoContext *context; FcConfigSetCurrent (config); fontmap = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT); if (! fontmap) g_error ("You are using a Pango that has been built against a cairo " "that lacks the Freetype font backend"); pango_cairo_font_map_set_resolution (PANGO_CAIRO_FONT_MAP (fontmap), 72.0 /* FIXME */); context = pango_font_map_create_context (fontmap); g_object_unref (fontmap); pika_font_factory_load_names (container, PANGO_FONT_MAP (fontmap), context); g_object_unref (context); FcConfigDestroy (config); } pika_container_thaw (container); } static void pika_font_factory_load (PikaFontFactory *factory, GError **error) { PikaContainer *container; Pika *pika; PikaAsyncSet *async_set; FcConfig *config; GFile *fonts_conf; GList *path; PikaAsync *async; async_set = pika_data_factory_get_async_set (PIKA_DATA_FACTORY (factory)); if (! pika_async_set_is_empty (async_set)) { /* font loading is already in progress */ return; } container = pika_data_factory_get_container (PIKA_DATA_FACTORY (factory)); pika = pika_data_factory_get_pika (PIKA_DATA_FACTORY (factory)); if (pika->be_verbose) g_print ("Loading fonts\n"); config = FcInitLoadConfig (); if (! config) return; fonts_conf = pika_directory_file (CONF_FNAME, NULL); if (! pika_font_factory_load_fonts_conf (config, fonts_conf)) g_printerr ("%s: failed to read '%s'.\n", G_STRFUNC, g_file_peek_path (fonts_conf)); g_object_unref (fonts_conf); fonts_conf = pika_sysconf_directory_file (CONF_FNAME, NULL); if (! pika_font_factory_load_fonts_conf (config, fonts_conf)) g_printerr ("%s: failed to read '%s'.\n", G_STRFUNC, g_file_peek_path (fonts_conf)); g_object_unref (fonts_conf); path = pika_data_factory_get_data_path (PIKA_DATA_FACTORY (factory)); if (! path) return; pika_container_freeze (container); pika_container_clear (container); pika_font_factory_add_directories (config, path, error); g_list_free_full (path, (GDestroyNotify) g_object_unref); /* We perform font cache initialization in a separate thread, so * in the case a cache rebuild is to be done it will not block * the UI. */ async = pika_parallel_run_async_independent_full ( +10, (PikaRunAsyncFunc) pika_font_factory_load_async, config); pika_async_add_callback_for_object ( async, (PikaAsyncCallback) pika_font_factory_load_async_callback, factory, factory); pika_async_set_add (async_set, async); g_object_unref (async); } static gboolean pika_font_factory_load_fonts_conf (FcConfig *config, GFile *fonts_conf) { gchar *path = g_file_get_path (fonts_conf); gboolean ret = TRUE; if (! FcConfigParseAndLoad (config, (const guchar *) path, FcFalse)) ret = FALSE; g_free (path); return ret; } static void pika_font_factory_add_directories (FcConfig *config, GList *path, GError **error) { GList *list; for (list = path; list; list = list->next) { /* The configured directories must exist or be created. */ g_file_make_directory_with_parents (list->data, NULL, NULL); /* Do not use FcConfigAppFontAddDir(). Instead use * FcConfigAppFontAddFile() with our own recursive loop. * Otherwise, when some fonts fail to load (e.g. permission * issues), we end up in weird situations where the fonts are in * the list, but are unusable and output many errors. * See bug 748553. */ pika_font_factory_recursive_add_fontdir (config, list->data, error); } if (error && *error) { gchar *font_list = g_strdup ((*error)->message); g_clear_error (error); g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Some fonts failed to load:\n%s"), font_list); g_free (font_list); } } static void pika_font_factory_recursive_add_fontdir (FcConfig *config, GFile *file, GError **error) { GFileEnumerator *enumerator; g_return_if_fail (config != NULL); enumerator = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (enumerator) { GFileInfo *info; while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL))) { GFileType file_type; GFile *child; if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) { g_object_unref (info); continue; } file_type = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE); child = g_file_enumerator_get_child (enumerator, info); if (file_type == G_FILE_TYPE_DIRECTORY) { pika_font_factory_recursive_add_fontdir (config, child, error); } else if (file_type == G_FILE_TYPE_REGULAR) { gchar *path = g_file_get_path (child); #ifdef G_OS_WIN32 gchar *tmp = g_win32_locale_filename_from_utf8 (path); g_free (path); /* XXX: g_win32_locale_filename_from_utf8() may return * NULL. So we need to check that path is not NULL before * trying to load with fontconfig. */ path = tmp; #endif if (! path || FcFalse == FcConfigAppFontAddFile (config, (const FcChar8 *) path)) { g_printerr ("%s: adding font file '%s' failed.\n", G_STRFUNC, path); if (error) { if (*error) { gchar *current_message = g_strdup ((*error)->message); g_clear_error (error); g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s\n- %s", current_message, path); g_free (current_message); } else { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "- %s", path); } } } g_free (path); } g_object_unref (child); g_object_unref (info); } g_object_unref (enumerator); } else { if (error) { gchar *path = g_file_get_path (file); if (*error) { gchar *current_message = g_strdup ((*error)->message); g_clear_error (error); g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s\n- %s%s", current_message, path, G_DIR_SEPARATOR_S); g_free (current_message); } else { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "- %s%s", path, G_DIR_SEPARATOR_S); } g_free (path); } } } static void pika_font_factory_add_font (PikaContainer *container, PangoContext *context, PangoFontDescription *desc, const gchar *full_name, const gchar *path, gpointer font_info[]) { gchar *name = (gchar *) full_name; if (! desc && ! full_name) return; if (! full_name) name = pango_font_description_to_string (desc); /* It doesn't look like pango_font_description_to_string() could ever * return NULL. But just to be double sure and avoid a segfault, I * check before validating the string. */ if (name && strlen (name) > 0 && g_utf8_validate (name, -1, NULL)) { PikaFont *font; font = g_object_new (PIKA_TYPE_FONT, "name", name, "pango-context", context, NULL); pika_font_set_lookup_name (font, pango_font_description_to_string (desc)); if (font_info != NULL) pika_font_set_font_info (font, font_info); if (path != NULL) { GFile *file; file = g_file_new_for_path (path); pika_data_set_file (PIKA_DATA (font), file, FALSE, FALSE); g_object_unref (file); } else { pika_data_make_internal (PIKA_DATA (font), "pika-font-standard-alias"); } pika_container_add (container, PIKA_OBJECT (font)); g_object_unref (font); } if (!full_name) g_free (name); } /* We're really chummy here with the implementation. Oh well. */ /* This is copied straight from make_alias_description in pango, plus * the pika_font_list_add_font bits. */ static void pika_font_factory_make_alias (PikaContainer *container, PangoContext *context, const gchar *family, gboolean bold, gboolean italic) { FcPattern *fcpattern; PangoFontDescription *desc = pango_font_description_new (); gchar *desc_str = NULL; gchar *style = NULL; gchar *psname = NULL; gchar *fullname = NULL; gint index = -1; gint weight = -1; gint width = -1; gint slant = -1; gint fontversion = -1; gpointer font_info[PROPERTIES_COUNT]; pango_font_description_set_family (desc, family); pango_font_description_set_style (desc, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); pango_font_description_set_variant (desc, PANGO_VARIANT_NORMAL); pango_font_description_set_weight (desc, bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL); pango_font_description_set_stretch (desc, PANGO_STRETCH_NORMAL); fcpattern = pango_fc_font_get_pattern (PANGO_FC_FONT (pango_context_load_font (context, desc))); /* this is for backward compatibility*/ desc_str = pango_font_description_to_string (desc); FcPatternGetString (fcpattern, FC_FULLNAME, 0, (FcChar8 **) &fullname); FcPatternGetString (fcpattern, FC_POSTSCRIPT_NAME, 0, (FcChar8 **) &psname); FcPatternGetString (fcpattern, FC_STYLE, 0, (FcChar8 **) &style); FcPatternGetInteger (fcpattern, FC_WEIGHT, 0, &weight); FcPatternGetInteger (fcpattern, FC_WIDTH, 0, &width); FcPatternGetInteger (fcpattern, FC_INDEX, 0, &index); FcPatternGetInteger (fcpattern, FC_SLANT, 0, &slant); FcPatternGetInteger (fcpattern, FC_FONTVERSION, 0, &fontversion); font_info[PROP_DESC] = (gpointer) desc_str; font_info[PROP_FULLNAME] = (gpointer) fullname; font_info[PROP_FAMILY] = (gpointer) family; font_info[PROP_STYLE] = (gpointer) style; font_info[PROP_PSNAME] = (gpointer) psname; font_info[PROP_WEIGHT] = (gpointer) &weight; font_info[PROP_WIDTH] = (gpointer) &width; font_info[PROP_INDEX] = (gpointer) &index; font_info[PROP_SLANT] = (gpointer) &slant; font_info[PROP_FONTVERSION] = (gpointer) &fontversion; /* This might be the only valid time where a NULL path is valid. Though I do * wonder if really these aliases are the right thing to do. Generic aliases * are the best way to have differing text renders over time (and that's not * something to be wished for). XXX */ pika_font_factory_add_font (container, context, desc, NULL, NULL, font_info); g_free (desc_str); pango_font_description_free (desc); } static void pika_font_factory_load_aliases (PikaContainer *container, PangoContext *context) { const gchar *families[] = { "Sans-serif", "Serif", "Monospace" }; gint i; for (i = 0; i < 3; i++) { pika_font_factory_make_alias (container, context, families[i], FALSE, FALSE); pika_font_factory_make_alias (container, context, families[i], TRUE, FALSE); pika_font_factory_make_alias (container, context, families[i], FALSE, TRUE); pika_font_factory_make_alias (container, context, families[i], TRUE, TRUE); } } static void pika_font_factory_load_names (PikaContainer *container, PangoFontMap *fontmap, PangoContext *context) { FcObjectSet *os; FcPattern *pat; FcFontSet *fontset; GString *ignored_fonts; gint n_ignored = 0; gint i; os = FcObjectSetBuild (FC_FAMILY, FC_STYLE, FC_POSTSCRIPT_NAME, FC_FULLNAME, FC_FILE, FC_WEIGHT, FC_SLANT, FC_WIDTH, FC_INDEX, FC_FONTVERSION, FC_FONTFORMAT, NULL); g_return_if_fail (os); pat = FcPatternCreate (); if (! pat) { FcObjectSetDestroy (os); g_critical ("%s: FcPatternCreate() returned NULL.", G_STRFUNC); return; } fontset = FcFontList (NULL, pat, os); ignored_fonts = g_string_new (NULL); FcPatternDestroy (pat); FcObjectSetDestroy (os); g_return_if_fail (fontset); for (i = 0; i < fontset->nfont; i++) { PangoFontDescription *pfd; GString *xml; gchar *fontformat; gchar *family = NULL; gchar *style = NULL; gchar *psname = NULL; gchar *newname = NULL; gchar *escaped_fullname = NULL; gchar *fullname = NULL; gchar *fullname2 = NULL; gchar *escaped_file = NULL; gchar *file = NULL; gint index = -1; gint weight = -1; gint width = -1; gint slant = -1; gint fontversion = -1; gpointer font_info[PROPERTIES_COUNT]; PangoFontDescription *pattern_pfd; gchar *pattern_pfd_desc; FcPatternGetString (fontset->fonts[i], FC_FILE, 0, (FcChar8 **) &file); /* * woff and woff2 cause problems with pango (probably with harfbuzz). * pcf,pcf.gz are bitmap font formats, not supported by pango (because of harfbuzz). * afm, pfm, pfb are type1 font formats, not supported by pango (because of harfbuzz). */ if (g_str_has_suffix (file, ".woff") || g_str_has_suffix (file, ".woff2") || g_str_has_suffix (file, ".pcf") || g_str_has_suffix (file, ".pcf.gz") || g_str_has_suffix (file, ".afm") || g_str_has_suffix (file, ".pfm") || g_str_has_suffix (file, ".pfb")) { g_string_append_printf (ignored_fonts, "- %s (not supported by pango)\n", file); n_ignored++; continue; } /* Pango doesn't support non SFNT fonts because harfbuzz doesn't support them. */ if (FcPatternGetString (fontset->fonts[i], FC_FONTFORMAT, 0, (FcChar8 **) &fontformat) != FcResultMatch || (g_ascii_strcasecmp (fontformat, "TrueType") != 0 && g_ascii_strcasecmp (fontformat, "CFF") != 0)) { g_string_append_printf (ignored_fonts, "- %s (non-SFNT font)\n", file); n_ignored++; continue; } /* Some variable fonts have only a family name and a font version. */ if (FcPatternGetString (fontset->fonts[i], FC_FULLNAME, 0, (FcChar8 **) &fullname) != FcResultMatch) { g_string_append_printf (ignored_fonts, "- %s (no full name)\n", file); n_ignored++; continue; } FcPatternGetString (fontset->fonts[i], FC_FAMILY, 0, (FcChar8 **) &family); FcPatternGetString (fontset->fonts[i], FC_POSTSCRIPT_NAME, 0, (FcChar8 **) &psname); FcPatternGetString (fontset->fonts[i], FC_STYLE, 0, (FcChar8 **) &style); FcPatternGetInteger (fontset->fonts[i], FC_WEIGHT, 0, &weight); FcPatternGetInteger (fontset->fonts[i], FC_WIDTH, 0, &width); FcPatternGetInteger (fontset->fonts[i], FC_INDEX, 0, &index); FcPatternGetInteger (fontset->fonts[i], FC_SLANT, 0, &slant); FcPatternGetInteger (fontset->fonts[i], FC_FONTVERSION, 0, &fontversion); /* this is for backward compatibility*/ pattern_pfd = pango_fc_font_description_from_pattern (fontset->fonts[i], FALSE); pattern_pfd_desc = pango_font_description_to_string (pattern_pfd); font_info[PROP_DESC] = (gpointer) pattern_pfd_desc; font_info[PROP_FULLNAME] = (gpointer) fullname; font_info[PROP_FAMILY] = (gpointer) family; font_info[PROP_STYLE] = (gpointer) style; font_info[PROP_PSNAME] = (gpointer) psname; font_info[PROP_WEIGHT] = (gpointer) &weight; font_info[PROP_WIDTH] = (gpointer) &width; font_info[PROP_INDEX] = (gpointer) &index; font_info[PROP_SLANT] = (gpointer) &slant; font_info[PROP_FONTVERSION] = (gpointer) &fontversion; /* Sometimes a font has more than one fullname, * sometimes the second is more appropriate for display, * in such cases we use it instead of the first. */ if (FcPatternGetString (fontset->fonts[i], FC_FULLNAME, 1, (FcChar8 **) &fullname2) != FcResultMatch) fullname2 = NULL; newname = g_strdup_printf ("pikafont%i", i); xml = g_string_new ("\n"); g_string_append_printf (xml, "%s", newname); escaped_fullname = g_markup_escape_text (fullname, -1); g_string_append_printf (xml, "%s", escaped_fullname); g_free (escaped_fullname); family = g_markup_escape_text (family, -1); g_string_append_printf (xml, "%s", family); g_free (family); escaped_file = g_markup_escape_text (file, -1); g_string_append_printf (xml, "%s", escaped_file); g_free (escaped_file); if (psname != NULL) { psname = g_markup_escape_text (psname, -1); g_string_append_printf (xml, "%s", psname); g_free (psname); } if (style != NULL) { style = g_markup_escape_text (style, -1); g_string_append_printf (xml, "%s", style); g_free (style); } if (weight != -1) g_string_append_printf (xml, "%i", weight); if (width != -1) g_string_append_printf (xml, "%i", width); if (slant != -1) g_string_append_printf (xml, "%i", slant); if (fontversion != -1) g_string_append_printf (xml, "%i", fontversion); if (index != -1) g_string_append_printf (xml, "%i", index); g_string_append (xml, "\n"); FcConfigParseAndLoadFromMemory (FcConfigGetCurrent (), (const FcChar8 *) xml->str, FcTrue); pfd = pango_font_description_from_string (newname); if (fullname2 != NULL && g_str_is_ascii (fullname2)) fullname = fullname2; pika_font_factory_add_font (container, context, pfd, fullname, (const gchar *) file, font_info); pango_font_description_free (pattern_pfd); g_free (pattern_pfd_desc); pango_font_description_free (pfd); g_free (newname); g_string_free (xml, TRUE); } if (n_ignored > 0) { if (g_getenv ("PIKA_DEBUG_FONTS") == NULL) g_printerr ("%s: %d unsupported fonts were ignored. Set the PIKA_DEBUG_FONTS environment variable for a listing.\n", G_STRFUNC, n_ignored); else g_printerr ("%s: %d unsupported fonts were ignored: %s", G_STRFUNC, n_ignored, ignored_fonts->str); } g_string_free (ignored_fonts, TRUE); /* only create aliases if there is at least one font available */ if (fontset->nfont > 0) pika_font_factory_load_aliases (container, context); FcFontSetDestroy (fontset); pika_font_class_set_font_factory (container); }