summaryrefslogtreecommitdiffstats
path: root/libass/ass_fontselect.c
diff options
context:
space:
mode:
authorGrigori Goronzy <greg@chown.ath.cx>2011-08-07 02:21:09 +0200
committerGrigori Goronzy <greg@chown.ath.cx>2015-07-10 10:41:01 +0200
commitc22a4ff9a395546637dbe0f1e9d0ee549dd0069a (patch)
tree40540f98c0faed9e3bac99ecc5caa0ef16b3aa92 /libass/ass_fontselect.c
parentd787615845d78d8f8e6d1a4ffc3dc3eecd8a92f6 (diff)
downloadlibass-c22a4ff9a395546637dbe0f1e9d0ee549dd0069a.tar.bz2
libass-c22a4ff9a395546637dbe0f1e9d0ee549dd0069a.tar.xz
Custom font matching and font sources
Implement a simple font sorter (FontSelector) and an interface to deal with multiple font sources (FontProvider). Unfinished business, but works for the most part. Currently the only implemented FontProvider uses fontconfig.
Diffstat (limited to 'libass/ass_fontselect.c')
-rw-r--r--libass/ass_fontselect.c514
1 files changed, 514 insertions, 0 deletions
diff --git a/libass/ass_fontselect.c b/libass/ass_fontselect.c
new file mode 100644
index 0000000..481d4c9
--- /dev/null
+++ b/libass/ass_fontselect.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
+ * Copyright (C) 2011 Grigori Goronzy <greg@chown.ath.cx>
+ *
+ * This file is part of libass.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_SFNT_NAMES_H
+#include <iconv.h>
+
+#include "ass_utils.h"
+#include "ass.h"
+#include "ass_library.h"
+#include "ass_fontselect.h"
+#include "ass_fontconfig.h"
+
+#define ABS(x) ((x) < 0 ? -(x) : (x))
+#define MAX_FULLNAME 100
+
+// proposed structure for holding font data, used for collection
+// and matching. strings are utf-8.
+struct font_info {
+ char *family; // family name
+ char **fullnames; // list of localized fullnames (e.g. Arial Bold Italic)
+ int n_fullname;
+
+ int slant;
+ int weight;
+
+ // how to access this face
+ char *path;
+ int index;
+
+ // similarity score
+ unsigned score;
+
+ // callbacks
+ ASS_FontProviderFuncs funcs;
+
+ // private data for callbacks
+ void *priv;
+};
+
+struct font_selector {
+ // fallbacks
+ char *family_default;
+ char *path_default;
+ int index_default;
+
+ // font database
+ int n_font;
+ int alloc_font;
+ ASS_FontInfo *font_infos;
+
+ // XXX: for now, manage a single provider
+ ASS_FontProvider *provider;
+};
+
+struct font_provider {
+ ASS_FontSelector *parent;
+ ASS_FontProviderFuncs funcs;
+ void *priv;
+};
+
+ASS_FontProvider *
+ass_font_provider_new(ASS_FontSelector *selector, ASS_FontProviderFuncs *funcs,
+ void *priv)
+{
+ ASS_FontProvider *provider = calloc(1, sizeof(ASS_FontProvider));
+
+ provider->parent = selector;
+ provider->funcs = *funcs;
+ provider->priv = priv;
+
+ return provider;
+}
+
+int
+ass_font_provider_add_font(ASS_FontProvider *provider,
+ ASS_FontProviderMetaData *meta, const char *path,
+ unsigned int index, void *data)
+{
+ int i;
+ ASS_FontSelector *selector = provider->parent;
+ ASS_FontInfo *info;
+
+ // TODO: sanity checks. do we have a path or valid get_face function?
+
+ // check size
+ if (selector->n_font >= selector->alloc_font) {
+ selector->alloc_font = FFMAX(1, 2 * selector->alloc_font);
+ selector->font_infos = realloc(selector->font_infos,
+ selector->alloc_font * sizeof(ASS_FontInfo));
+ }
+
+ // copy over metadata
+ info = selector->font_infos + selector->n_font;
+ memset(info, 0, sizeof(ASS_FontInfo));
+
+ info->slant = meta->slant;
+ info->weight = meta->weight;
+ info->family = strdup(meta->family);
+ info->n_fullname = meta->n_fullname;
+ info->fullnames = calloc(meta->n_fullname, sizeof(char *));
+
+ for (i = 0; i < info->n_fullname; i++)
+ info->fullnames[i] = strdup(meta->fullnames[i]);
+
+ if (path)
+ info->path = strdup(path);
+
+ info->index = index;
+ info->priv = data;
+ info->funcs = provider->funcs;
+
+ selector->n_font++;
+
+ return 1;
+}
+
+void ass_font_provider_free(ASS_FontProvider *provider)
+{
+ // TODO: this should probably remove all fonts that belong
+ // to this provider from the list
+
+ if (provider->funcs.destroy_provider)
+ provider->funcs.destroy_provider(provider->priv);
+ free(provider);
+}
+
+
+
+/**
+ * \brief Compare a font (a) against a font request (b). Records
+ * a matching score - the lower the better.
+ * \param a font
+ * \param b font request
+ * \return matching score
+ */
+static unsigned font_info_similarity(ASS_FontInfo *a, ASS_FontInfo *b)
+{
+ int i, j;
+ unsigned similarity = 0;
+
+ // compare fullnames
+ // a matching fullname is very nice and instantly drops the score to zero
+ similarity = 10000;
+ for (i = 0; i < a->n_fullname; i++)
+ for (j = 0; j < b->n_fullname; j++) {
+ if (ABS(strcasecmp(a->fullnames[i], b->fullnames[j])) == 0)
+ similarity = 0;
+ }
+
+ // if we don't have any match, compare fullnames against family
+ // sometimes the family name is used similarly
+ if (similarity > 0) {
+ for (i = 0; i < b->n_fullname; i++) {
+ if (ABS(strcasecmp(a->family, b->fullnames[i])) == 0)
+ similarity = 0;
+ }
+ }
+
+ // compare shortened family, if no fullname matches
+ if (similarity > 0 && ABS(strcasecmp(a->family, b->family)) == 0)
+ similarity = 1000;
+
+ // nothing found? Try fallback fonts
+ // XXX: add more than arial
+ if (similarity > 1000 && ABS(strcasecmp(a->family, "Arial")) == 0)
+ similarity = 2000;
+
+ // compare slant
+ similarity += ABS(a->slant - b->slant);
+
+ // compare weight
+ similarity += ABS(a->weight - b->weight);
+
+ return similarity;
+}
+
+// calculate scores
+static void font_info_req_similarity(ASS_FontInfo *font_infos, size_t len,
+ ASS_FontInfo *req)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ font_infos[i].score = font_info_similarity(&font_infos[i], req);
+}
+
+#if 1
+// dump font information
+static void font_info_dump(ASS_FontInfo *font_infos, size_t len)
+{
+ int i, j;
+
+ // dump font infos
+ for (i = 0; i < len; i++) {
+ printf("font %d\n", i);
+ printf(" family: '%s'\n", font_infos[i].family);
+ printf(" fullnames: ");
+ for (j = 0; j < font_infos[i].n_fullname; j++)
+ printf("'%s' ", font_infos[i].fullnames[j]);
+ printf("\n");
+ printf(" slant: %d\n", font_infos[i].slant);
+ printf(" weight: %d\n", font_infos[i].weight);
+ printf(" path: %s\n", font_infos[i].path);
+ printf(" index: %d\n", font_infos[i].index);
+ printf(" score: %d\n", font_infos[i].score);
+
+ }
+}
+#endif
+
+static int font_info_compare(const void *av, const void *bv)
+{
+ const ASS_FontInfo *a = av;
+ const ASS_FontInfo *b = bv;
+
+ return a->score - b->score;
+}
+
+static char *select_font(ASS_FontSelector *priv, ASS_Library *library,
+ const char *family, unsigned bold,
+ unsigned italic, int *index, uint32_t code)
+{
+ int num_fonts = priv->n_font;
+ ASS_FontInfo *font_infos = priv->font_infos;
+ ASS_FontInfo req;
+ char *req_fullname;
+
+ // do we actually have any fonts?
+ if (!priv->n_font)
+ return NULL;
+
+ // fill font request
+ memset(&req, 0, sizeof(ASS_FontInfo));
+ req.slant = italic;
+ req.weight = bold;
+ req.n_fullname = 1;
+ req.fullnames = &req_fullname;
+ req.fullnames[0] = (char *)family;
+ req.family = strdup(family);
+ char *p = strchr(req.family, ' ');
+ if (p) *p = 0;
+
+ // calculate similarities
+ font_info_req_similarity(font_infos, num_fonts, &req);
+
+ // sort
+ qsort(font_infos, num_fonts, sizeof(ASS_FontInfo),
+ font_info_compare);
+
+ // check glyph coverage
+ int info_index = 0;
+ while (info_index < priv->n_font && font_infos[info_index].funcs.check_glyph
+ && font_infos[info_index].funcs.check_glyph(font_infos[info_index].priv, code) == 0)
+ info_index++;
+
+ free(req.family);
+
+ // return best match
+ if (!font_infos[info_index].path)
+ return NULL;
+ *index = font_infos[info_index].index;
+ return strdup(font_infos[info_index].path);
+}
+
+
+/**
+ * \brief Find a font. Use default family or path if necessary.
+ * \param library ASS library handle
+ * \param family font family
+ * \param treat_family_as_pattern treat family as fontconfig pattern
+ * \param bold font weight value
+ * \param italic font slant value
+ * \param index out: font index inside a file
+ * \param code: the character that should be present in the font, can be 0
+ * \return font file path
+*/
+char *ass_font_select(ASS_FontSelector *priv, ASS_Library *library,
+ const char *family, unsigned bold, unsigned italic,
+ int *index, uint32_t code)
+{
+ char *res = 0;
+
+ if (family && *family)
+ res = select_font(priv, library, family, bold, italic, index, code);
+
+ if (!res && priv->family_default) {
+ res = select_font(priv, library, priv->family_default, bold,
+ italic, index, code);
+ if (res)
+ ass_msg(library, MSGL_WARN, "fontselect: Using default "
+ "font family: (%s, %d, %d) -> %s, %d",
+ family, bold, italic, res, *index);
+ }
+
+ if (!res && priv->path_default) {
+ res = strdup(priv->path_default);
+ *index = priv->index_default;
+ ass_msg(library, MSGL_WARN, "fontselect: Using default font: "
+ "(%s, %d, %d) -> %s, %d", family, bold, italic,
+ res, *index);
+ }
+
+ if (!res) {
+ res = select_font(priv, library, "Arial", bold, italic,
+ index, code);
+ if (res)
+ ass_msg(library, MSGL_WARN, "fontselect: Using 'Arial' "
+ "font family: (%s, %d, %d) -> %s, %d", family, bold,
+ italic, res, *index);
+ }
+
+ if (res)
+ ass_msg(library, MSGL_V,
+ "fontselect: (%s, %d, %d) -> %s, %d", family, bold,
+ italic, res, *index);
+
+ return res;
+}
+
+static int get_font_info(FT_Library lib, FT_Face face, ASS_FontInfo *info)
+{
+ int i;
+ int num_fullname = 0;
+ int num_names = FT_Get_Sfnt_Name_Count(face);
+ int slant, weight;
+ char *fullnames[100];
+ char *family = NULL;
+ iconv_t utf16to8;
+
+ // we're only interested in outlines
+ if (!(face->face_flags & FT_FACE_FLAG_SCALABLE))
+ return 0;
+
+ // scan font names
+ utf16to8 = iconv_open("UTF-8", "UTF-16BE");
+ for (i = 0; i < num_names && num_fullname < 100; i++) {
+ FT_SfntName name;
+ FT_Get_Sfnt_Name(face, i, &name);
+ //printf("name %d pid %d eid %d lid %d nameid %d\n",
+ // i, name.platform_id, name.encoding_id, name.language_id, name.name_id);
+ // we add both full names and alternate family names to the list of full names
+ if (name.platform_id == 3 && (name.name_id == 4 || name.name_id == 1)) {
+ char buf[1024];
+ char *bufptr = buf;
+ size_t inbytes = name.string_len;
+ size_t outbytes = 1024;
+ iconv(utf16to8, (char**)&name.string, &inbytes, &bufptr, &outbytes);
+ *bufptr = '\0';
+ // no primary family name yet - just use the first we encounter as a best guess
+ if (family == NULL && name.name_id == 1) {
+ family = strdup(buf);
+ continue;
+ }
+ fullnames[num_fullname] = strdup(buf);
+ num_fullname++;
+ }
+ }
+ iconv_close(utf16to8);
+
+ // check if we got a valid family - if not use whatever FreeType gives us
+ if (family == NULL)
+ family = strdup(face->family_name);
+
+ // calculate sensible slant and weight from style attributes
+ slant = 110 * !!(face->style_flags & FT_STYLE_FLAG_ITALIC);
+ weight = 120 * !!(face->style_flags & FT_STYLE_FLAG_BOLD) + 80;
+
+ // fill our struct
+ info->family = family;
+ info->slant = slant;
+ info->weight = weight;
+ info->fullnames = calloc(sizeof(char *), num_fullname);
+ memcpy(info->fullnames, &fullnames, sizeof(char *) * num_fullname);
+ info->n_fullname = num_fullname;
+
+ return 1;
+}
+
+/**
+ * \brief Process memory font.
+ * \param priv private data
+ * \param library library object
+ * \param ftlibrary freetype library object
+ * \param idx index of the processed font in library->fontdata
+ *
+ * Builds a font pattern in memory via FT_New_Memory_Face/FcFreeTypeQueryFace.
+*/
+static void process_fontdata(ASS_FontSelector *priv, ASS_Library *library,
+ FT_Library ftlibrary, int idx)
+{
+ int rc;
+ const char *name = library->fontdata[idx].name;
+ const char *data = library->fontdata[idx].data;
+ int data_size = library->fontdata[idx].size;
+
+ FT_Face face;
+ int face_index, num_faces = 1;
+
+ for (face_index = 0; face_index < num_faces; ++face_index) {
+ rc = FT_New_Memory_Face(ftlibrary, (unsigned char *) data,
+ data_size, face_index, &face);
+ if (rc) {
+ ass_msg(library, MSGL_WARN, "Error opening memory font: %s",
+ name);
+ return;
+ }
+ num_faces = face->num_faces;
+
+ // get font metadata and add to list
+ ASS_FontInfo info;
+ memset(&info, 0, sizeof(ASS_FontInfo));
+ if (!get_font_info(ftlibrary, face, &info))
+ continue;
+ info.index = face_index;
+ info.path = strdup(name);
+
+ priv->font_infos = realloc(priv->font_infos, sizeof(ASS_FontInfo) * (priv->n_font + 1));
+ memcpy(priv->font_infos + priv->n_font, &info, sizeof(ASS_FontInfo));
+ priv->n_font++;
+
+ FT_Done_Face(face);
+ }
+}
+
+/**
+ * \brief Init font selector.
+ * \param library libass library object
+ * \param ftlibrary freetype library object
+ * \param family default font family
+ * \param path default font path
+ * \return newly created font selector
+ */
+ASS_FontSelector *
+ass_fontselect_init(ASS_Library *library,
+ FT_Library ftlibrary, const char *family,
+ const char *path)
+{
+ int i;
+ ASS_FontSelector *priv = calloc(1, sizeof(ASS_FontSelector));
+
+ priv->family_default = family ? strdup(family) : NULL;
+ priv->path_default = path ? strdup(path) : NULL;
+ priv->index_default = 0;
+
+ // XXX: for now, always add the fontconfig provider
+ priv->provider = ass_fontconfig_add_provider(library, priv, NULL);
+
+ // XXX: use a real font provider for this
+ for (i = 0; i < library->num_fontdata; ++i)
+ process_fontdata(priv, library, ftlibrary, i);
+
+ return priv;
+}
+
+/**
+ * \brief Free font selector and release associated data
+ *
+ */
+void ass_fontselect_free(ASS_FontSelector *priv)
+{
+ int i;
+
+ if (priv) {
+ for (i = 0; i < priv->n_font; i++) {
+ ASS_FontInfo *info = priv->font_infos + i;
+ int j;
+ for (j = 0; j < info->n_fullname; j++)
+ free(info->fullnames[j]);
+ free(info->fullnames);
+ free(info->family);
+ if (info->path)
+ free(info->path);
+ if (info->funcs.destroy_font)
+ info->funcs.destroy_font(info->priv);
+ }
+ free(priv->font_infos);
+ free(priv->path_default);
+ free(priv->family_default);
+ }
+
+ // TODO: we should track all child font providers and
+ // free them here
+ ass_font_provider_free(priv->provider);
+
+ free(priv);
+}