From d1af7dcb4b206d85ae8fc344c2feadaa774c4f07 Mon Sep 17 00:00:00 2001 From: Caleb Hearon Date: Sat, 7 Aug 2021 15:53:55 +0000 Subject: [PATCH] select fonts via postscript name on Linux greatly improves font matching accuracy Fixes #1572 --- .github/workflows/prebuild.yaml | 2 +- src/register_font.cc | 62 +++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/.github/workflows/prebuild.yaml b/.github/workflows/prebuild.yaml index b10cc1522..0b784dde4 100644 --- a/.github/workflows/prebuild.yaml +++ b/.github/workflows/prebuild.yaml @@ -29,7 +29,7 @@ jobs: name: ${{ matrix.canvas_tag}}, Node.js ${{ matrix.node }}, Linux runs-on: ubuntu-latest container: - image: chearon/canvas-prebuilt:7 + image: chearon/canvas-prebuilt:9 env: CANVAS_VERSION_TO_BUILD: ${{ matrix.canvas_tag }} steps: diff --git a/src/register_font.cc b/src/register_font.cc index 927a66b4b..3b9e45149 100644 --- a/src/register_font.cc +++ b/src/register_font.cc @@ -10,6 +10,7 @@ #include #else #include +#include #endif #include @@ -32,11 +33,29 @@ #define PREFERRED_ENCODING_ID TT_MS_ID_UNICODE_CS #endif +// With PangoFcFontMaps (the pango font module on Linux) we're able to add a +// hook that lets us get perfect matching. Tie the conditions for enabling that +// feature to one variable +#if !defined(__APPLE__) && !defined(_WIN32) && PANGO_VERSION_CHECK(1, 47, 0) +#define PERFECT_MATCHES_ENABLED +#endif + #define IS_PREFERRED_ENC(X) \ X.platform_id == PREFERRED_PLATFORM_ID && X.encoding_id == PREFERRED_ENCODING_ID +#ifdef PERFECT_MATCHES_ENABLED +// On Linux-like OSes using FontConfig, the PostScript name ranks higher than +// preferred family and family name since we'll use it to get perfect font +// matching (see fc_font_map_substitute_hook) +#define GET_NAME_RANK(X) \ + ((IS_PREFERRED_ENC(X) ? 1 : 0) << 2) | \ + ((X.name_id == TT_NAME_ID_PS_NAME ? 1 : 0) << 1) | \ + (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0) +#else #define GET_NAME_RANK(X) \ - (IS_PREFERRED_ENC(X) ? 1 : 0) + (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0) + ((IS_PREFERRED_ENC(X) ? 1 : 0) << 1) | \ + (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0) +#endif /* * Return a UTF-8 encoded string given a TrueType name buf+len @@ -104,7 +123,13 @@ get_family_name(FT_Face face) { for (unsigned i = 0; i < FT_Get_Sfnt_Name_Count(face); ++i) { FT_Get_Sfnt_Name(face, i, &name); - if (name.name_id == TT_NAME_ID_FONT_FAMILY || name.name_id == TT_NAME_ID_PREFERRED_FAMILY) { + if ( + name.name_id == TT_NAME_ID_FONT_FAMILY || +#ifdef PERFECT_MATCHES_ENABLED + name.name_id == TT_NAME_ID_PS_NAME || +#endif + name.name_id == TT_NAME_ID_PREFERRED_FAMILY + ) { char *buf = to_utf8(name.string, name.string_len, name.platform_id, name.encoding_id); if (buf) { @@ -113,6 +138,18 @@ get_family_name(FT_Face face) { best_rank = rank; if (best_buf) free(best_buf); best_buf = buf; + +#ifdef PERFECT_MATCHES_ENABLED + // Prepend an '@' to the family name + if (name.name_id == TT_NAME_ID_PS_NAME) { + size_t len = strlen(buf); + best_buf = (char *)malloc(len + 2); + best_buf[0] = '@'; + strncpy(best_buf + 1, buf, len); + best_buf[len + 1] = '\0'; + free(buf); + } +#endif } else { free(buf); } @@ -209,6 +246,21 @@ get_pango_font_description(unsigned char* filepath) { return NULL; } +#ifdef PERFECT_MATCHES_ENABLED +static void +fc_font_map_substitute_hook(FcPattern *pat, gpointer data) { + FcChar8 *family; + + for (int i = 0; FcPatternGetString(pat, FC_FAMILY, i, &family) == FcResultMatch; i++) { + if (family[0] == '@') { + FcPatternAddString(pat, FC_POSTSCRIPT_NAME, (FcChar8 *)family + 1); + FcPatternRemove(pat, FC_FAMILY, i); + i -= 1; + } + } +} +#endif + /* * Register font with the OS */ @@ -233,6 +285,12 @@ register_font(unsigned char *filepath) { // font families. pango_cairo_font_map_set_default(NULL); +#ifdef PERFECT_MATCHES_ENABLED + PangoFontMap* map = pango_cairo_font_map_get_default(); + PangoFcFontMap* fc_map = PANGO_FC_FONT_MAP(map); + pango_fc_font_map_set_default_substitute(fc_map, fc_font_map_substitute_hook, NULL, NULL); +#endif + return true; }