so

What font is that in?

Volume 4, Issue 34; 07 Jul 2020

Missing glyph U+2BC3, you say? Ok. Where is it?

A couple of months ago, I stumbled over a missing glyph message while trying to generate a PDF. It didn’t take too long to find a very helpful answer from Martin Monperrus on superuser. I gleefully stole it.

Over the past few weeks, I’ve edited that script maybe half a dozen times to change the search character. Last night, I decided to just fix it:

#!/usr/bin/env python3

"""Given a Unicode character reference, return a list of the fonts
that contain that character. Credit to Martin Monperrus in
https://superuser.com/questions/876572 for the actual code. I just
wrapped a bit of Click around it."""

import sys
import os
import unicodedata
import click
from fontTools.ttLib import TTFont

# YMMV.
FONTPATH = [os.path.expanduser("~") + "/Library/Fonts",
            os.path.expanduser("~") + "/fonts",
            "/System/Library/Fonts",
            "/Library/Fonts",
            "/usr/local/AHFormatterV70/fonts"]


@click.command()
@click.argument('charref')
def checkfonts(charref):
    """Looks for the character identified by charref in FONTPATH. The
    charref can be a decimal number, a hexadecimal number optionally
    preceded by "U+", or a single (Unicode) character. "65", "U+41"
    and "A" are all equivalent. It searches for that character in the
    (TrueType) fonts that it finds on FONTPATH.
    """

    if len(charref) > 1:
        try:
            codepoint = int(charref)
        except ValueError:
            try:
                if charref.startswith('U+'):
                    codepoint = int(charref[2:], 16)
                else:
                    codepoint = int(charref, 16)
            except ValueError:
                print("Bad character reference:", charref)
                sys.exit(1)
    else:
        codepoint = ord(charref)

    char = chr(codepoint)

    fonts = findfonts()
    count = 0
    for fontpath in fonts:
        font = TTFont(fontpath)
        if char_in_font(char, font):
            count += 1
            print(fontpath)

    # Space before ";" below is intentional so that
    # wide characters don't overlap the semicolon.
    if count == 0:
        print("Could not find '%s' (%s ; codepoint=U+%X) in %d fonts"
              % (charref, char, codepoint, len(fonts)))
    elif count == 1:
        print("contains '%s' (%s ; codepoint=U+%X)"
              % (unicodedata.name(char), char, codepoint))
    else:
        print("%d fonts contain '%s' (%s ; codepoint=U+%X)"
              % (count, unicodedata.name(char), char, codepoint))


def findfonts():
    """Returns all the TrueType fonts that are found on FONTPATH."""
    fonts = []
    for path in FONTPATH:
        for root, dirs, files in os.walk(path):
            for file in files:
                if file.endswith(".ttf"):
                    fonts.append(os.path.join(root, file))
    return fonts


def char_in_font(unicode_char, font):
    """Tests to see if unicode_char appears in font."""
    for cmap in font['cmap'].tables:
        if cmap.isUnicode():
            if ord(unicode_char) in cmap.cmap:
                return True
    return False


if __name__ == "__main__":
    checkfonts()

Oh, and by the way,

$ font-check 2bc3
/Users/ndw/Library/Fonts/Symbola_hint.ttf
contains 'HORIZONTAL BLACK OCTAGON' (⯃ ; codepoint=U+2BC3)

Share and enjoy.