899 lines
31 KiB
C
899 lines
31 KiB
C
|
/* 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 <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 "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 <fontconfig/fontconfig.h>
|
||
|
|
||
|
#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_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)
|
||
|
{
|
||
|
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 ("<?xml version=\"1.0\"?>\n<match>");
|
||
|
|
||
|
g_string_append_printf (xml,
|
||
|
"<test name=\"family\"><string>%s</string></test>",
|
||
|
newname);
|
||
|
|
||
|
escaped_fullname = g_markup_escape_text (fullname, -1);
|
||
|
g_string_append_printf (xml,
|
||
|
"<edit name=\"fullname\" mode=\"assign\" binding=\"strong\"><string>%s</string></edit>",
|
||
|
escaped_fullname);
|
||
|
g_free (escaped_fullname);
|
||
|
|
||
|
family = g_markup_escape_text (family, -1);
|
||
|
g_string_append_printf (xml,
|
||
|
"<edit name=\"family\" mode=\"assign\" binding=\"strong\"><string>%s</string></edit>",
|
||
|
family);
|
||
|
g_free (family);
|
||
|
|
||
|
escaped_file = g_markup_escape_text (file, -1);
|
||
|
g_string_append_printf (xml,
|
||
|
"<edit name=\"file\" mode=\"assign\" binding=\"strong\"><string>%s</string></edit>",
|
||
|
escaped_file);
|
||
|
g_free (escaped_file);
|
||
|
|
||
|
if (psname != NULL)
|
||
|
{
|
||
|
psname = g_markup_escape_text (psname, -1);
|
||
|
g_string_append_printf (xml,
|
||
|
"<edit name=\"postscriptname\" mode=\"assign\" binding=\"strong\"><string>%s</string></edit>",
|
||
|
psname);
|
||
|
g_free (psname);
|
||
|
}
|
||
|
|
||
|
if (style != NULL)
|
||
|
{
|
||
|
style = g_markup_escape_text (style, -1);
|
||
|
g_string_append_printf (xml,
|
||
|
"<edit name=\"style\" mode=\"assign\" binding=\"strong\"><string>%s</string></edit>",
|
||
|
style);
|
||
|
g_free (style);
|
||
|
}
|
||
|
|
||
|
if (weight != -1)
|
||
|
g_string_append_printf (xml,
|
||
|
"<edit name=\"weight\" mode=\"assign\" binding=\"strong\"><int>%i</int></edit>",
|
||
|
weight);
|
||
|
|
||
|
if (width != -1)
|
||
|
g_string_append_printf (xml,
|
||
|
"<edit name=\"width\" mode=\"assign\" binding=\"strong\"><int>%i</int></edit>",
|
||
|
width);
|
||
|
|
||
|
if (slant != -1)
|
||
|
g_string_append_printf (xml,
|
||
|
"<edit name=\"slant\" mode=\"assign\" binding=\"strong\"><int>%i</int></edit>",
|
||
|
slant);
|
||
|
|
||
|
if (fontversion != -1)
|
||
|
g_string_append_printf (xml,
|
||
|
"<edit name=\"fontversion\" mode=\"assign\" binding=\"strong\"><int>%i</int></edit>",
|
||
|
fontversion);
|
||
|
|
||
|
if (index != -1)
|
||
|
g_string_append_printf (xml,
|
||
|
"<edit name=\"index\" mode=\"assign\" binding=\"strong\"><int>%i</int></edit>",
|
||
|
index);
|
||
|
|
||
|
g_string_append (xml, "</match>\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);
|
||
|
}
|