Files
gimp/app/text/gimpfontfactory.c
Jehan c5f9b8e190 Issue #5530: do not fail font loading on broken user/GIMP fonts.conf.
Additionally to loading the default fontconfig configuration file, GIMP
also looks up /etc/gimp/<version>/fonts.conf and
$XDG_CONFIG_HOME/GIMP/<version>/fonts.conf (or equivalent in other
OSes). If these don't exist (which is the most common case), this is not
considered a bug. Fontconfig had a regression bug of
FcConfigParseAndLoad() in 2.13.92, which was fixed in a later commit:
fcada52291
As a consequence of this bug, font loading failed in Windows when these
non-mandatory files were absent.

The current commit, originally proposed by Jacob Boerema (@Wormnest) and
slightly reviewed works around the issue, because anyway there is never
any reason why failing to load these files should break font loading as
a general rule. Even if these files exist and are broken (wrong syntax
or whatnot), we should just output some warning on stderr and continue
loading without these additional confs.
With fontconfig 2.13.92, warnings will be also outputted (wrongly), but
at least it won't block loading anymore.

Also let's unref() the `config` object even when the whole font loading
succeeds. Man of FcConfigSetCurrent() clearly says that the reference
count of config is incremented since 2.12.0 (our current minimum
fontconfig is 2.12.4) so let's not leak one reference.
2020-08-16 18:06:09 +02:00

678 lines
21 KiB
C

/* 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 */