diff options
Diffstat (limited to 'app/text/gimpfontfactory.c')
-rw-r--r-- | app/text/gimpfontfactory.c | 677 |
1 files changed, 677 insertions, 0 deletions
diff --git a/app/text/gimpfontfactory.c b/app/text/gimpfontfactory.c new file mode 100644 index 0000000..ead75d5 --- /dev/null +++ b/app/text/gimpfontfactory.c @@ -0,0 +1,677 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfontfactory.c + * Copyright (C) 2003-2018 Michael Natterer <mitch@gimp.org> + * + * Partly based on code Copyright (C) Sven Neumann <sven@gimp.org> + * Manish Singh <yosh@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 <pango/pangocairo.h> +#include <pango/pangofc-fontmap.h> +#include <gegl.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" + +#include "text-types.h" + +#include "core/gimp.h" +#include "core/gimp-parallel.h" +#include "core/gimpasync.h" +#include "core/gimpasyncset.h" +#include "core/gimpcancelable.h" +#include "core/gimpcontainer.h" + +#include "gimpfont.h" +#include "gimpfontfactory.h" + +#include "gimp-intl.h" + + +/* Use fontconfig directly for speed. We can use the pango stuff when/if + * fontconfig/pango get more efficient. + */ +#define USE_FONTCONFIG_DIRECTLY + +#ifdef USE_FONTCONFIG_DIRECTLY +#include <fontconfig/fontconfig.h> +#endif + +#define CONF_FNAME "fonts.conf" + + +struct _GimpFontFactoryPrivate +{ + gpointer foo; /* can't have an empty struct */ +}; + +#define GET_PRIVATE(obj) (((GimpFontFactory *) (obj))->priv) + + +static void gimp_font_factory_data_init (GimpDataFactory *factory, + GimpContext *context); +static void gimp_font_factory_data_refresh (GimpDataFactory *factory, + GimpContext *context); +static void gimp_font_factory_data_save (GimpDataFactory *factory); +static void gimp_font_factory_data_cancel (GimpDataFactory *factory); +static GimpData * gimp_font_factory_data_duplicate (GimpDataFactory *factory, + GimpData *data); +static gboolean gimp_font_factory_data_delete (GimpDataFactory *factory, + GimpData *data, + gboolean delete_from_disk, + GError **error); + +static void gimp_font_factory_load (GimpFontFactory *factory, + GError **error); +static gboolean gimp_font_factory_load_fonts_conf (FcConfig *config, + GFile *fonts_conf); +static void gimp_font_factory_add_directories (FcConfig *config, + GList *path, + GError **error); +static void gimp_font_factory_recursive_add_fontdir + (FcConfig *config, + GFile *file, + GError **error); +static void gimp_font_factory_load_names (GimpContainer *container, + PangoFontMap *fontmap, + PangoContext *context); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpFontFactory, gimp_font_factory, + GIMP_TYPE_DATA_FACTORY) + +#define parent_class gimp_font_factory_parent_class + + +static void +gimp_font_factory_class_init (GimpFontFactoryClass *klass) +{ + GimpDataFactoryClass *factory_class = GIMP_DATA_FACTORY_CLASS (klass); + + factory_class->data_init = gimp_font_factory_data_init; + factory_class->data_refresh = gimp_font_factory_data_refresh; + factory_class->data_save = gimp_font_factory_data_save; + factory_class->data_cancel = gimp_font_factory_data_cancel; + factory_class->data_duplicate = gimp_font_factory_data_duplicate; + factory_class->data_delete = gimp_font_factory_data_delete; +} + +static void +gimp_font_factory_init (GimpFontFactory *factory) +{ + factory->priv = gimp_font_factory_get_instance_private (factory); +} + +static void +gimp_font_factory_data_init (GimpDataFactory *factory, + GimpContext *context) +{ + GError *error = NULL; + + gimp_font_factory_load (GIMP_FONT_FACTORY (factory), &error); + + if (error) + { + gimp_message_literal (gimp_data_factory_get_gimp (factory), NULL, + GIMP_MESSAGE_INFO, + error->message); + g_error_free (error); + } +} + +static void +gimp_font_factory_data_refresh (GimpDataFactory *factory, + GimpContext *context) +{ + GError *error = NULL; + + gimp_font_factory_load (GIMP_FONT_FACTORY (factory), &error); + + if (error) + { + gimp_message_literal (gimp_data_factory_get_gimp (factory), NULL, + GIMP_MESSAGE_INFO, + error->message); + g_error_free (error); + } +} + +static void +gimp_font_factory_data_save (GimpDataFactory *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 (! gimp_async_set_is_empty (gimp_data_factory_get_async_set (factory))) + return; + + /* Reinit the library with defaults. */ + FcInitReinitialize (); +} + +static void +gimp_font_factory_data_cancel (GimpDataFactory *factory) +{ + GimpAsyncSet *async_set = gimp_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 + * gimp_font_factory_load_async_callback() that loading was canceled and the + * factory might be dead, and that it should just do nothing. + */ + gimp_cancelable_cancel (GIMP_CANCELABLE (async_set)); + gimp_async_set_clear (async_set); +} + +static GimpData * +gimp_font_factory_data_duplicate (GimpDataFactory *factory, + GimpData *data) +{ + return NULL; +} + +static gboolean +gimp_font_factory_data_delete (GimpDataFactory *factory, + GimpData *data, + gboolean delete_from_disk, + GError **error) +{ + return TRUE; +} + + +/* public functions */ + +GimpDataFactory * +gimp_font_factory_new (Gimp *gimp, + const gchar *path_property_name) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (path_property_name != NULL, NULL); + + return g_object_new (GIMP_TYPE_FONT_FACTORY, + "gimp", gimp, + "data-type", GIMP_TYPE_FONT, + "path-property-name", path_property_name, + "get-standard-func", gimp_font_get_standard, + NULL); +} + + +/* private functions */ + +static void +gimp_font_factory_load_async (GimpAsync *async, + FcConfig *config) +{ + if (FcConfigBuildFonts (config)) + { + gimp_async_finish (async, config); + } + else + { + FcConfigDestroy (config); + + gimp_async_abort (async); + } +} + +static void +gimp_font_factory_load_async_callback (GimpAsync *async, + GimpFontFactory *factory) +{ + GimpContainer *container; + + /* the operation was canceled and the factory might be dead (see + * gimp_font_factory_data_cancel()). bail. + */ + if (gimp_async_is_canceled (async)) + return; + + container = gimp_data_factory_get_container (GIMP_DATA_FACTORY (factory)); + + if (gimp_async_is_finished (async)) + { + FcConfig *config = gimp_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); + + gimp_font_factory_load_names (container, PANGO_FONT_MAP (fontmap), context); + g_object_unref (context); + FcConfigDestroy (config); + } + + gimp_container_thaw (container); +} + +static void +gimp_font_factory_load (GimpFontFactory *factory, + GError **error) +{ + GimpContainer *container; + Gimp *gimp; + GimpAsyncSet *async_set; + FcConfig *config; + GFile *fonts_conf; + GList *path; + GimpAsync *async; + + async_set = gimp_data_factory_get_async_set (GIMP_DATA_FACTORY (factory)); + + if (! gimp_async_set_is_empty (async_set)) + { + /* font loading is already in progress */ + return; + } + + container = gimp_data_factory_get_container (GIMP_DATA_FACTORY (factory)); + + gimp = gimp_data_factory_get_gimp (GIMP_DATA_FACTORY (factory)); + + if (gimp->be_verbose) + g_print ("Loading fonts\n"); + + config = FcInitLoadConfig (); + + if (! config) + return; + + fonts_conf = gimp_directory_file (CONF_FNAME, NULL); + if (! gimp_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 = gimp_sysconf_directory_file (CONF_FNAME, NULL); + if (! gimp_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 = gimp_data_factory_get_data_path (GIMP_DATA_FACTORY (factory)); + if (! path) + return; + + gimp_container_freeze (container); + gimp_container_clear (container); + + gimp_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 = gimp_parallel_run_async_independent_full ( + +10, + (GimpRunAsyncFunc) gimp_font_factory_load_async, + config); + + gimp_async_add_callback_for_object ( + async, + (GimpAsyncCallback) gimp_font_factory_load_async_callback, + factory, + factory); + + gimp_async_set_add (async_set, async); + + g_object_unref (async); +} + +static gboolean +gimp_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 +gimp_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. + */ + gimp_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 +gimp_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_is_hidden (info)) + { + g_object_unref (info); + continue; + } + + file_type = g_file_info_get_file_type (info); + child = g_file_enumerator_get_child (enumerator, info); + + if (file_type == G_FILE_TYPE_DIRECTORY) + { + gimp_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 +gimp_font_factory_add_font (GimpContainer *container, + PangoContext *context, + PangoFontDescription *desc) +{ + gchar *name; + + if (! desc) + return; + + 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)) + { + GimpFont *font; + + font = g_object_new (GIMP_TYPE_FONT, + "name", name, + "pango-context", context, + NULL); + + gimp_container_add (container, GIMP_OBJECT (font)); + g_object_unref (font); + } + + g_free (name); +} + +#ifdef USE_FONTCONFIG_DIRECTLY +/* We're really chummy here with the implementation. Oh well. */ + +/* This is copied straight from make_alias_description in pango, plus + * the gimp_font_list_add_font bits. + */ +static void +gimp_font_factory_make_alias (GimpContainer *container, + PangoContext *context, + const gchar *family, + gboolean bold, + gboolean italic) +{ + PangoFontDescription *desc = pango_font_description_new (); + + 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); + + gimp_font_factory_add_font (container, context, desc); + + pango_font_description_free (desc); +} + +static void +gimp_font_factory_load_aliases (GimpContainer *container, + PangoContext *context) +{ + const gchar *families[] = { "Sans-serif", "Serif", "Monospace" }; + gint i; + + for (i = 0; i < 3; i++) + { + gimp_font_factory_make_alias (container, context, families[i], + FALSE, FALSE); + gimp_font_factory_make_alias (container, context, families[i], + TRUE, FALSE); + gimp_font_factory_make_alias (container, context, families[i], + FALSE, TRUE); + gimp_font_factory_make_alias (container, context, families[i], + TRUE, TRUE); + } +} + +static void +gimp_font_factory_load_names (GimpContainer *container, + PangoFontMap *fontmap, + PangoContext *context) +{ + FcObjectSet *os; + FcPattern *pat; + FcFontSet *fontset; + gint i; + + os = FcObjectSetBuild (FC_FAMILY, FC_STYLE, + FC_SLANT, FC_WEIGHT, FC_WIDTH, + 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); + + FcPatternDestroy (pat); + FcObjectSetDestroy (os); + + g_return_if_fail (fontset); + + for (i = 0; i < fontset->nfont; i++) + { + PangoFontDescription *desc; + + desc = pango_fc_font_description_from_pattern (fontset->fonts[i], FALSE); + gimp_font_factory_add_font (container, context, desc); + pango_font_description_free (desc); + } + + /* only create aliases if there is at least one font available */ + if (fontset->nfont > 0) + gimp_font_factory_load_aliases (container, context); + + FcFontSetDestroy (fontset); +} + +#else /* ! USE_FONTCONFIG_DIRECTLY */ + +static void +gimp_font_factory_load_names (GimpContainer *container, + PangoFontMap *fontmap, + PangoContext *context) +{ + PangoFontFamily **families; + PangoFontFace **faces; + gint n_families; + gint n_faces; + gint i, j; + + pango_font_map_list_families (fontmap, &families, &n_families); + + for (i = 0; i < n_families; i++) + { + pango_font_family_list_faces (families[i], &faces, &n_faces); + + for (j = 0; j < n_faces; j++) + { + PangoFontDescription *desc; + + desc = pango_font_face_describe (faces[j]); + gimp_font_factory_add_font (container, context, desc); + pango_font_description_free (desc); + } + } + + g_free (families); +} + +#endif /* USE_FONTCONFIG_DIRECTLY */ |