summaryrefslogtreecommitdiffstats
path: root/libass/ass_fontselect.c
diff options
context:
space:
mode:
authorGrigori Goronzy <greg@chown.ath.cx>2015-09-07 11:40:54 +0200
committerGrigori Goronzy <greg@chown.ath.cx>2015-09-07 11:40:54 +0200
commita2f12d6f79bfdc6bc7c1cb6ddd3a42c97ab2de4f (patch)
tree2600406db3d71a17d4389f34ba4bf66f7c098247 /libass/ass_fontselect.c
parent23de9995103c16ceceba19cae87e328be12fa8e4 (diff)
parent0268c64b8ec7bef9287a212759152bf7d15e64d8 (diff)
downloadlibass-a2f12d6f79bfdc6bc7c1cb6ddd3a42c97ab2de4f.tar.bz2
libass-a2f12d6f79bfdc6bc7c1cb6ddd3a42c97ab2de4f.tar.xz
Merge branch 'fonts'
Diffstat (limited to 'libass/ass_fontselect.c')
-rw-r--r--libass/ass_fontselect.c1043
1 files changed, 1043 insertions, 0 deletions
diff --git a/libass/ass_fontselect.c b/libass/ass_fontselect.c
new file mode 100644
index 0000000..d48b170
--- /dev/null
+++ b/libass/ass_fontselect.c
@@ -0,0 +1,1043 @@
+/*
+ * 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 <limits.h>
+#include <assert.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <ft2build.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include FT_FREETYPE_H
+#include FT_SFNT_NAMES_H
+#include FT_TRUETYPE_IDS_H
+#include <iconv.h>
+
+#include "ass_utils.h"
+#include "ass.h"
+#include "ass_library.h"
+#include "ass_fontselect.h"
+#include "ass_fontconfig.h"
+#include "ass_coretext.h"
+#include "ass_directwrite.h"
+#include "ass_font.h"
+
+#define ABS(x) ((x) < 0 ? -(x) : (x))
+#define MAX_FULLNAME 100
+
+// internal font database element
+// all strings are utf-8
+struct font_info {
+ int uid; // unique font face id
+
+ char **families; // family name
+ char **fullnames; // list of localized fullnames (e.g. Arial Bold Italic)
+ int n_family;
+ int n_fullname;
+
+ int slant;
+ int weight; // TrueType scale, 100-900
+ int width;
+
+ // how to access this face
+ char *path; // absolute path
+ int index; // font index inside font collections
+ char *postscript_name; // can be used as an alternative to index to
+ // identify a font inside a collection
+
+ // font source
+ ASS_FontProvider *provider;
+
+ // private data for callbacks
+ void *priv;
+};
+
+struct font_selector {
+ // uid counter
+ int uid;
+
+ // fallbacks
+ char *family_default;
+ char *path_default;
+ int index_default;
+
+ // font database
+ int n_font;
+ int alloc_font;
+ ASS_FontInfo *font_infos;
+
+ ASS_FontProvider *default_provider;
+ ASS_FontProvider *embedded_provider;
+};
+
+struct font_provider {
+ ASS_FontSelector *parent;
+ ASS_FontProviderFuncs funcs;
+ void *priv;
+};
+
+typedef struct font_data_ft FontDataFT;
+struct font_data_ft {
+ ASS_Library *lib;
+ FT_Face face;
+ int idx;
+};
+
+static int check_glyph_ft(void *data, uint32_t codepoint)
+{
+ FontDataFT *fd = (FontDataFT *)data;
+
+ if (!codepoint)
+ return 1;
+
+ return !!FT_Get_Char_Index(fd->face, codepoint);
+}
+
+static void destroy_font_ft(void *data)
+{
+ FontDataFT *fd = (FontDataFT *)data;
+
+ FT_Done_Face(fd->face);
+ free(fd);
+}
+
+static size_t
+get_data_embedded(void *data, unsigned char *buf, size_t offset, size_t len)
+{
+ FontDataFT *ft = (FontDataFT *)data;
+ ASS_Fontdata *fd = ft->lib->fontdata;
+ int i = ft->idx;
+
+ if (buf == NULL)
+ return fd[i].size;
+
+ if (offset >= fd[i].size)
+ return 0;
+
+ if (len > fd[i].size - offset)
+ len = fd[i].size - offset;
+
+ memcpy(buf, fd[i].data + offset, len);
+ return len;
+}
+
+static ASS_FontProviderFuncs ft_funcs = {
+ get_data_embedded,
+ check_glyph_ft,
+ destroy_font_ft,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void load_fonts_from_dir(ASS_Library *library, const char *dir)
+{
+ DIR *d = opendir(dir);
+ if (!d)
+ return;
+ while (1) {
+ struct dirent *entry = readdir(d);
+ if (!entry)
+ break;
+ char fullname[PATH_MAX];
+ snprintf(fullname, sizeof(fullname), "%s/%s", dir, entry->d_name);
+ size_t bufsize = 0;
+ ass_msg(library, MSGL_WARN, "Loading font file '%s'", fullname);
+ void *data = read_file(library, fullname, &bufsize);
+ if (data) {
+ ass_add_font(library, entry->d_name, data, bufsize);
+ free(data);
+ }
+ }
+ closedir(d);
+}
+
+/**
+ * \brief Create a bare font provider.
+ * \param selector parent selector. The provider will be attached to it.
+ * \param funcs callback/destroy functions
+ * \param data private data of the provider
+ * \return the font provider
+ */
+ASS_FontProvider *
+ass_font_provider_new(ASS_FontSelector *selector, ASS_FontProviderFuncs *funcs,
+ void *data)
+{
+ ASS_FontProvider *provider = calloc(1, sizeof(ASS_FontProvider));
+ if (provider == NULL)
+ return NULL;
+
+ provider->parent = selector;
+ provider->funcs = *funcs;
+ provider->priv = data;
+
+ return provider;
+}
+
+/**
+ * Free all data associated with a FontInfo struct. Handles FontInfo structs
+ * with incomplete allocations well.
+ *
+ * \param info FontInfo struct to free associated data from
+ */
+static void ass_font_provider_free_fontinfo(ASS_FontInfo *info)
+{
+ int j;
+
+ if (info->fullnames) {
+ for (j = 0; j < info->n_fullname; j++)
+ free(info->fullnames[j]);
+ free(info->fullnames);
+ }
+
+ if (info->families) {
+ for (j = 0; j < info->n_family; j++)
+ free(info->families[j]);
+ free(info->families);
+ }
+
+ if (info->path)
+ free(info->path);
+
+ if (info->postscript_name)
+ free(info->postscript_name);
+
+}
+
+/**
+ * \brief Add a font to a font provider.
+ * \param provider the font provider
+ * \param meta basic metadata of the font
+ * \param path path to the font file, or NULL
+ * \param index face index inside the file
+ * \param psname PostScript name of the face (overrides index if present)
+ * \param data private data for the font
+ * \return success
+ */
+int
+ass_font_provider_add_font(ASS_FontProvider *provider,
+ ASS_FontProviderMetaData *meta, const char *path,
+ unsigned int index, const char *psname, void *data)
+{
+ int i;
+ int weight, slant, width;
+ ASS_FontSelector *selector = provider->parent;
+ ASS_FontInfo *info;
+
+#if 0
+ int j;
+ printf("new font:\n");
+ printf(" families: ");
+ for (j = 0; j < meta->n_family; j++)
+ printf("'%s' ", meta->families[j]);
+ printf("\n");
+ printf(" fullnames: ");
+ for (j = 0; j < meta->n_fullname; j++)
+ printf("'%s' ", meta->fullnames[j]);
+ printf("\n");
+ printf(" slant: %d\n", meta->slant);
+ printf(" weight: %d\n", meta->weight);
+ printf(" width: %d\n", meta->width);
+ printf(" path: %s\n", path);
+ printf(" index: %d\n", index);
+#endif
+
+ weight = meta->weight;
+ slant = meta->slant;
+ width = meta->width;
+
+ // check slant/weight for validity, use defaults if they're invalid
+ if (weight < 100 || weight > 900)
+ weight = 400;
+ if (slant < 0 || slant > 110)
+ slant = 0;
+ if (width < 50 || width > 200)
+ width = 100;
+
+ // 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));
+
+ // set uid
+ info->uid = selector->uid++;
+
+ info->slant = slant;
+ info->weight = weight;
+ info->width = width;
+ info->n_fullname = meta->n_fullname;
+ info->n_family = meta->n_family;
+ info->families = calloc(meta->n_family, sizeof(char *));
+ if (meta->n_fullname) {
+ info->fullnames = calloc(meta->n_fullname, sizeof(char *));
+ if (info->fullnames == NULL)
+ goto error;
+ }
+
+ if (info->families == NULL)
+ goto error;
+
+ for (i = 0; i < info->n_family; i++) {
+ info->families[i] = strdup(meta->families[i]);
+ if (info->families[i] == NULL)
+ goto error;
+ }
+
+ for (i = 0; i < info->n_fullname; i++) {
+ info->fullnames[i] = strdup(meta->fullnames[i]);
+ if (info->fullnames[i] == NULL)
+ goto error;
+ }
+
+ if (path) {
+ info->path = strdup(path);
+ if (info->path == NULL)
+ goto error;
+ }
+
+ if (psname) {
+ info->postscript_name = strdup(psname);
+ if (info->postscript_name == NULL)
+ goto error;
+ }
+
+ info->index = index;
+ info->priv = data;
+ info->provider = provider;
+
+ selector->n_font++;
+ return 0;
+
+error:
+ ass_font_provider_free_fontinfo(info);
+ return 1;
+}
+
+/**
+ * \brief Clean up font database. Deletes all fonts that have an invalid
+ * font provider (NULL).
+ * \param selector the font selector
+ */
+static void ass_fontselect_cleanup(ASS_FontSelector *selector)
+{
+ int i, w;
+
+ for (i = 0, w = 0; i < selector->n_font; i++) {
+ ASS_FontInfo *info = selector->font_infos + i;
+
+ // update write pointer
+ if (info->provider != NULL) {
+ // rewrite, if needed
+ if (w != i)
+ memcpy(selector->font_infos + w, selector->font_infos + i,
+ sizeof(ASS_FontInfo));
+ w++;
+ }
+
+ }
+
+ selector->n_font = w;
+}
+
+void ass_font_provider_free(ASS_FontProvider *provider)
+{
+ int i;
+ ASS_FontSelector *selector = provider->parent;
+
+ // free all fonts and mark their entries
+ for (i = 0; i < selector->n_font; i++) {
+ ASS_FontInfo *info = selector->font_infos + i;
+
+ if (info->provider == provider) {
+ ass_font_provider_free_fontinfo(info);
+
+ if (info->provider->funcs.destroy_font)
+ info->provider->funcs.destroy_font(info->priv);
+
+ info->provider = NULL;
+ }
+
+ }
+
+ // delete marked entries
+ ass_fontselect_cleanup(selector);
+
+ // free private data of the provider
+ if (provider->funcs.destroy_provider)
+ provider->funcs.destroy_provider(provider->priv);
+
+ free(provider);
+}
+
+/**
+ * \brief Return whether the given font is in the given family.
+ */
+static bool matches_family_name(ASS_FontInfo *f, const char *family)
+{
+ for (int i = 0; i < f->n_family; i++) {
+ if (strcasecmp(f->families[i], family) == 0)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * \brief Return whether the given font has the given fullname.
+ */
+static bool matches_fullname(ASS_FontInfo *f, const char *fullname)
+{
+ for (int i = 0; i < f->n_fullname; i++) {
+ if (strcasecmp(f->fullnames[i], fullname) == 0)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * \brief Compare attributes of font (a) against a font request (req). Returns
+ * a matching score - the lower the better.
+ * Ignores font names/families!
+ * \param a font
+ * \param b font request
+ * \return matching score
+ */
+static unsigned font_attributes_similarity(ASS_FontInfo *a, ASS_FontInfo *req)
+{
+ unsigned similarity = 0;
+ similarity += ABS(a->weight - req->weight);
+ similarity += ABS(a->slant - req->slant);
+ similarity += ABS(a->width - req->width);
+
+ return similarity;
+}
+
+#if 0
+// 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(" families: ");
+ for (j = 0; j < font_infos[i].n_family; j++)
+ printf("'%s' ", font_infos[i].families[j]);
+ 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(" width: %d\n", font_infos[i].width);
+ 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 check_glyph(ASS_FontInfo *fi, uint32_t code)
+{
+ ASS_FontProvider *provider = fi->provider;
+ assert(provider && provider->funcs.check_glyph);
+
+ return provider->funcs.check_glyph(fi->priv, code);
+}
+
+static char *
+find_font(ASS_FontSelector *priv, ASS_Library *library,
+ ASS_FontProviderMetaData meta, unsigned bold, unsigned italic,
+ int *index, char **postscript_name, int *uid, ASS_FontStream *stream,
+ uint32_t code, bool *name_match)
+{
+ ASS_FontInfo req = {0};
+ ASS_FontInfo *selected = NULL;
+
+ // do we actually have any fonts?
+ if (!priv->n_font)
+ return NULL;
+
+ // fill font request
+ req.slant = italic;
+ req.weight = bold;
+ req.width = 100;
+
+ // Match font family name against font list
+ unsigned score_min = UINT_MAX;
+ for (int i = 0; i < meta.n_fullname; i++) {
+ const char *fullname = meta.fullnames[i];
+
+ for (int x = 0; x < priv->n_font; x++) {
+ ASS_FontInfo *font = &priv->font_infos[x];
+ unsigned score = UINT_MAX;
+
+ if (matches_family_name(font, fullname)) {
+ // If there's a family match, compare font attributes
+ // to determine best match in that particular family
+ score = font_attributes_similarity(font, &req);
+ *name_match = true;
+ } else if (matches_fullname(font, fullname)) {
+ // If we don't have any match, compare fullnames against request
+ // if there is a match now, assign lowest score possible. This means
+ // the font should be chosen instantly, without further search.
+ score = 0;
+ *name_match = true;
+ }
+
+ // Consider updating idx if score is better than current minimum
+ if (score < score_min) {
+ // Check if the font has the requested glyph.
+ // We are doing this here, for every font face, because
+ // coverage might differ between the variants of a font
+ // family. In practice, it is common that the regular
+ // style has the best coverage while bold/italic/etc
+ // variants cover less (e.g. FreeSans family).
+ // We want to be able to match even if the closest variant
+ // does not have the requested glyph, but another member
+ // of the family has the glyph.
+ if (!check_glyph(font, code))
+ continue;
+
+ score_min = score;
+ selected = font;
+ }
+
+ // Lowest possible score instantly matches; this is typical
+ // for fullname matches, but can also occur with family matches.
+ if (score == 0)
+ break;
+ }
+
+ // The list of names is sorted by priority. If we matched anything,
+ // we can and should stop.
+ if (selected != NULL)
+ break;
+ }
+
+ // found anything?
+ char *result = NULL;
+ if (selected) {
+ // successfully matched, set up return values
+ *postscript_name = selected->postscript_name;
+ *index = selected->index;
+ *uid = selected->uid;
+
+ // set up memory stream if there is no path
+ if (selected->path == NULL) {
+ ASS_FontProvider *provider = selected->provider;
+ stream->func = provider->funcs.get_data;
+ stream->priv = selected->priv;
+ // Prefer PostScript name because it is unique. This is only
+ // used for display purposes so it doesn't matter that much,
+ // though.
+ if (selected->postscript_name)
+ result = selected->postscript_name;
+ else
+ result = selected->families[0];
+ } else
+ result = selected->path;
+ }
+
+ return result;
+}
+
+static char *select_font(ASS_FontSelector *priv, ASS_Library *library,
+ const char *family, unsigned bold, unsigned italic,
+ int *index, char **postscript_name, int *uid,
+ ASS_FontStream *stream, uint32_t code)
+{
+ ASS_FontProvider *default_provider = priv->default_provider;
+ ASS_FontProviderMetaData meta = {0};
+ char *family_trim = strdup_trimmed(family);
+ char *result = NULL;
+ bool name_match = false;
+
+ if (family_trim == NULL)
+ return NULL;
+
+ ASS_FontProviderMetaData default_meta = {
+ .n_fullname = 1,
+ .fullnames = &family_trim,
+ };
+
+ // Get a list of substitutes if applicable, and use it for matching.
+ if (default_provider && default_provider->funcs.get_substitutions) {
+ default_provider->funcs.get_substitutions(default_provider->priv,
+ family_trim, &meta);
+ }
+
+ if (!meta.n_fullname) {
+ meta = default_meta;
+ }
+
+ result = find_font(priv, library, meta, bold, italic, index,
+ postscript_name, uid, stream, code, &name_match);
+
+ // If no matching font was found, it might not exist in the font list
+ // yet. Call the match_fonts callback to fill in the missing fonts
+ // on demand, and retry the search for a match.
+ if (result == NULL && name_match == false && default_provider &&
+ default_provider->funcs.match_fonts) {
+ // TODO: consider changing the API to make more efficient
+ // implementations possible.
+ for (int i = 0; i < meta.n_fullname; i++) {
+ default_provider->funcs.match_fonts(library, default_provider,
+ meta.fullnames[i]);
+ }
+ result = find_font(priv, library, meta, bold, italic, index,
+ postscript_name, uid, stream, code, &name_match);
+ }
+
+ // cleanup
+ free(family_trim);
+ if (meta.fullnames != default_meta.fullnames) {
+ for (int i = 0; i < meta.n_fullname; i++)
+ free(meta.fullnames[i]);
+ free(meta.fullnames);
+ }
+
+ return result;
+}
+
+
+/**
+ * \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,
+ ASS_Font *font, int *index, char **postscript_name,
+ int *uid, ASS_FontStream *data, uint32_t code)
+{
+ char *res = 0;
+ const char *family = font->desc.family;
+ unsigned bold = font->desc.bold;
+ unsigned italic = font->desc.italic;
+ ASS_FontProvider *default_provider = priv->default_provider;
+
+ if (family && *family)
+ res = select_font(priv, library, family, bold, italic, index,
+ postscript_name, uid, data, code);
+
+ if (!res && priv->family_default) {
+ res = select_font(priv, library, priv->family_default, bold,
+ italic, index, postscript_name, uid, data, code);
+ if (res)
+ ass_msg(library, MSGL_WARN, "fontselect: Using default "
+ "font family: (%s, %d, %d) -> %s, %d, %s",
+ family, bold, italic, res, *index, *postscript_name);
+ }
+
+ if (!res && default_provider && default_provider->funcs.get_fallback) {
+ const char *search_family = family;
+ if (!search_family || !*search_family)
+ search_family = "Arial";
+ char *fallback_family = default_provider->funcs.get_fallback(
+ default_provider->priv, search_family, code);
+
+ if (fallback_family) {
+ res = select_font(priv, library, fallback_family, bold, italic,
+ index, postscript_name, uid, data, code);
+ free(fallback_family);
+ }
+ }
+
+ 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, %s", family, bold, italic,
+ priv->path_default, *index, *postscript_name);
+ }
+
+ if (res)
+ ass_msg(library, MSGL_V,
+ "fontselect: (%s, %d, %d) -> %s, %d, %s", family, bold,
+ italic, res, *index, *postscript_name);
+
+ return res;
+}
+
+
+/**
+ * \brief Read basic metadata (names, weight, slant) from a FreeType face,
+ * as required for the FontSelector for matching and sorting.
+ * \param lib FreeType library
+ * \param face FreeType face
+ * \param info metadata, returned here
+ * \return success
+ */
+static int
+get_font_info(FT_Library lib, FT_Face face, ASS_FontProviderMetaData *info)
+{
+ int i;
+ int num_fullname = 0;
+ int num_family = 0;
+ int num_names = FT_Get_Sfnt_Name_Count(face);
+ int slant, weight;
+ char *fullnames[MAX_FULLNAME];
+ char *families[MAX_FULLNAME];
+ 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");
+
+ if (utf16to8 == (iconv_t)-1)
+ goto error;
+
+ for (i = 0; i < num_names; i++) {
+ FT_SfntName name;
+
+ if (FT_Get_Sfnt_Name(face, i, &name))
+ continue;
+
+ if (name.platform_id == TT_PLATFORM_MICROSOFT &&
+ (name.name_id == TT_NAME_ID_FULL_NAME ||
+ name.name_id == TT_NAME_ID_FONT_FAMILY)) {
+ char buf[1024];
+ char *bufptr = buf;
+ size_t inbytes = name.string_len;
+ size_t outbytes = 1024;
+
+ if (iconv(utf16to8, (char**)&name.string, &inbytes, &bufptr,
+ &outbytes) == (size_t)-1)
+ continue;
+
+ *bufptr = '\0';
+
+ if (name.name_id == TT_NAME_ID_FULL_NAME) {
+ fullnames[num_fullname] = strdup_trimmed(buf);
+ if (fullnames[num_fullname] == NULL)
+ goto error;
+ num_fullname++;
+ }
+
+ if (name.name_id == TT_NAME_ID_FONT_FAMILY) {
+ families[num_family] = strdup_trimmed(buf);
+ if (families[num_family] == NULL)
+ goto error;
+ num_family++;
+ }
+ }
+
+ }
+ iconv_close(utf16to8);
+ utf16to8 = (iconv_t)-1;
+
+ // check if we got a valid family - if not use whatever FreeType gives us
+ if (num_family == 0 && face->family_name) {
+ families[0] = strdup(face->family_name);
+ if (families[0] == NULL)
+ goto error;
+ num_family++;
+ }
+
+ // we absolutely need a name
+ if (num_family == 0)
+ goto error;
+
+ // calculate sensible slant and weight from style attributes
+ slant = 110 * !!(face->style_flags & FT_STYLE_FLAG_ITALIC);
+ weight = 300 * !!(face->style_flags & FT_STYLE_FLAG_BOLD) + 400;
+
+ // fill our struct
+ info->slant = slant;
+ info->weight = weight;
+ info->width = 100; // FIXME, should probably query the OS/2 table
+ info->families = calloc(sizeof(char *), num_family);
+
+ if (info->families == NULL)
+ goto error;
+
+ memcpy(info->families, &families, sizeof(char *) * num_family);
+ info->n_family = num_family;
+
+ if (num_fullname) {
+ info->fullnames = calloc(sizeof(char *), num_fullname);
+ if (info->fullnames == NULL)
+ goto error;
+ memcpy(info->fullnames, &fullnames, sizeof(char *) * num_fullname);
+ info->n_fullname = num_fullname;
+ }
+
+ return 0;
+
+error:
+ if (utf16to8 != (iconv_t)-1)
+ iconv_close(utf16to8);
+
+ for (i = 0; i < num_family; i++)
+ free(families[i]);
+
+ for (i = 0; i < num_fullname; i++)
+ free(fullnames[i]);
+
+ free(info->families);
+ free(info->fullnames);
+
+ return 1;
+}
+
+/**
+ * \brief Free the dynamically allocated fields of metadata
+ * created by get_font_info.
+ * \param meta metadata created by get_font_info
+ */
+static void free_font_info(ASS_FontProviderMetaData *meta)
+{
+ int i;
+
+ for (i = 0; i < meta->n_family; i++)
+ free(meta->families[i]);
+
+ for (i = 0; i < meta->n_fullname; i++)
+ free(meta->fullnames[i]);
+
+ free(meta->families);
+ free(meta->fullnames);
+}
+
+/**
+ * \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 FontInfo with FreeType and some table reading.
+*/
+static void process_fontdata(ASS_FontProvider *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) {
+ ASS_FontProviderMetaData info;
+ FontDataFT *ft;
+
+ 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);
+ continue;
+ }
+
+ num_faces = face->num_faces;
+
+ charmap_magic(library, face);
+
+ memset(&info, 0, sizeof(ASS_FontProviderMetaData));
+ if (get_font_info(ftlibrary, face, &info)) {
+ ass_msg(library, MSGL_WARN,
+ "Error getting metadata for embedded font '%s'", name);
+ FT_Done_Face(face);
+ continue;
+ }
+
+ ft = calloc(1, sizeof(FontDataFT));
+
+ if (ft == NULL) {
+ free_font_info(&info);
+ FT_Done_Face(face);
+ continue;
+ }
+
+ ft->lib = library;
+ ft->face = face;
+ ft->idx = idx;
+
+ if (ass_font_provider_add_font(priv, &info, NULL, face_index,
+ NULL, ft)) {
+ ass_msg(library, MSGL_WARN, "Failed to add embedded font '%s'",
+ name);
+ }
+
+ free_font_info(&info);
+ }
+}
+
+/**
+ * \brief Create font provider for embedded fonts. This parses the fonts known
+ * to the current ASS_Library and adds them to the selector.
+ * \param lib library
+ * \param selector font selector
+ * \param ftlib FreeType library - used for querying fonts
+ * \return font provider
+ */
+static ASS_FontProvider *
+ass_embedded_fonts_add_provider(ASS_Library *lib, ASS_FontSelector *selector,
+ FT_Library ftlib)
+{
+ int i;
+ ASS_FontProvider *priv = ass_font_provider_new(selector, &ft_funcs, NULL);
+ if (priv == NULL)
+ return NULL;
+
+ if (lib->fonts_dir && lib->fonts_dir[0]) {
+ load_fonts_from_dir(lib, lib->fonts_dir);
+ }
+
+ for (i = 0; i < lib->num_fontdata; ++i)
+ process_fontdata(priv, lib, ftlib, i);
+
+ return priv;
+}
+
+struct font_constructors {
+ ASS_DefaultFontProvider id;
+ ASS_FontProvider *(*constructor)(ASS_Library *, ASS_FontSelector *,
+ const char *);
+};
+
+struct font_constructors font_constructors[] = {
+#ifdef CONFIG_CORETEXT
+ { ASS_FONTPROVIDER_CORETEXT, &ass_coretext_add_provider },
+#endif
+#ifdef CONFIG_DIRECTWRITE
+ { ASS_FONTPROVIDER_DIRECTWRITE, &ass_directwrite_add_provider },
+#endif
+#ifdef CONFIG_FONTCONFIG
+ { ASS_FONTPROVIDER_FONTCONFIG, &ass_fontconfig_add_provider },
+#endif
+ { ASS_FONTPROVIDER_NONE, NULL },
+};
+
+/**
+ * \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, const char *config,
+ ASS_DefaultFontProvider dfp)
+{
+ ASS_FontSelector *priv = calloc(1, sizeof(ASS_FontSelector));
+ if (priv == NULL)
+ return NULL;
+
+ priv->uid = 1;
+ priv->family_default = family ? strdup(family) : NULL;
+ priv->path_default = path ? strdup(path) : NULL;
+ priv->index_default = 0;
+
+ priv->embedded_provider = ass_embedded_fonts_add_provider(library, priv,
+ ftlibrary);
+
+ if (priv->embedded_provider == NULL) {
+ ass_msg(library, MSGL_WARN, "failed to create embedded font provider");
+ }
+
+ if (dfp >= ASS_FONTPROVIDER_AUTODETECT) {
+ for (int i = 0; font_constructors[i].constructor; i++ )
+ if (dfp == font_constructors[i].id ||
+ dfp == ASS_FONTPROVIDER_AUTODETECT) {
+ priv->default_provider =
+ font_constructors[i].constructor(library, priv, config);
+ if (priv->default_provider)
+ break;
+ }
+
+ if (!priv->default_provider)
+ ass_msg(library, MSGL_WARN, "can't find selected font provider");
+
+ }
+
+ return priv;
+}
+
+void ass_get_available_font_providers(ASS_Library *priv,
+ ASS_DefaultFontProvider **providers,
+ size_t *size)
+{
+ size_t offset = 2;
+
+ *size = offset;
+ for (int i = 0; font_constructors[i].constructor; i++)
+ (*size)++;
+
+ *providers = calloc(*size, sizeof(ASS_DefaultFontProvider));
+
+ if (*providers == NULL) {
+ *size = (size_t)-1;
+ return;
+ }
+
+ (*providers)[0] = ASS_FONTPROVIDER_NONE;
+ (*providers)[1] = ASS_FONTPROVIDER_AUTODETECT;
+
+ for (int i = offset; i < *size; i++)
+ (*providers)[i] = font_constructors[i-offset].id;
+}
+
+/**
+ * \brief Free font selector and release associated data
+ * \param the font selector
+ */
+void ass_fontselect_free(ASS_FontSelector *priv)
+{
+ if (priv->default_provider)
+ ass_font_provider_free(priv->default_provider);
+ if (priv->embedded_provider)
+ ass_font_provider_free(priv->embedded_provider);
+
+ free(priv->font_infos);
+ free(priv->path_default);
+ free(priv->family_default);
+
+ free(priv);
+}