bedstead/compatcheck
Ben Harris 81c554424a Check OS/2.achVendID in compatcheck
My GNU Emacs configuration ended up using this to select its default
font, so it should remain constant within a major version.
2024-12-29 18:50:52 +00:00

97 lines
3.4 KiB
Python
Executable File

#! /usr/bin/python3
# SPDX-License-Identifier: CC0-1.0
# This is a script to check the backward compatibility of fonts, and
# specifically of Bedstead. It takes two font files as arguments and
# checks whether upgrades from the first to the second will break
# anything. It should fail on anything that would require a change of
# major version number.
#
# Things that are checked:
#
# Glyph names. Existing PostScript files that refer to specific glyph
# names should continue to work. The script checks that all glyph
# names in the old font still exist, but not that they look right.
#
# Code points. Anything using a font through the 'cmap' table should
# continue to work. That means that any 'cmap' that exists should
# continue to exist, and that any mapping through any 'cmap' should
# continue to work. As with glyph names, the script checks for the
# existence of mappings, but not their content.
#
# Names. Applications use a font's name to refer to it. This script
# checks that the subset of names for which this seems reasonable
# match between the old and new fonts.
#
# Vendor ID. At least some versions of GNU Emacs like to use the
# OS/2.achVendID field to select fonts. So if that changes, Emacs
# might not find your favourite font.
from argparse import ArgumentParser
from fontTools import ttLib
from sys import exit
parser = ArgumentParser()
parser.add_argument("old")
parser.add_argument("new")
cfg = parser.parse_args()
ttold = ttLib.TTFont(cfg.old)
ttnew = ttLib.TTFont(cfg.new)
failed = False
def fail(msg):
failed = True
print(f"FAIL: {msg}")
if not (set(ttold.getGlyphOrder()) <= set(ttnew.getGlyphOrder())):
fail("Glyphs vanished: "
f"{set(ttold.getGlyphOrder()) - set(ttnew.getGlyphOrder())!r}")
for cmapold in ttold['cmap'].tables:
cmapnew = ttnew['cmap'].getcmap(cmapold.platformID, cmapold.platEncID)
if cmapnew == None:
fail("No cmap in new font for "
f"{(cmapold.platformID,cmapold.platEncID)}")
elif not (set(cmapold.cmap.keys()) <= set(cmapnew.cmap.keys())):
fail("Code points vanished from "
f"{(cmapold.platformID,cmapold.platEncID)}: "
f"{set(cmapold.cmap.keys()) - set(cmapnew.cmap.keys())!r}")
# Names that might be used to specify fonts.
specnames = {
1, # Font family name
2, # Font subfamily name
4, # Full font name
6, # PostScript FontName
16, # Typographic family name
17, # Typographic subfamily name
18, # Compatible full name
20, # PostScript CID findfont name
21, # WWS family name
22, # WWS subfamily name
25, # Variations PostScript name prefix
}
for oldname in ttold['name'].names:
if oldname.nameID in specnames:
newname = ttnew['name'].getName(oldname.nameID, oldname.platformID,
oldname.platEncID, oldname.langID)
if newname == None:
fail("No name in new font for "
f"nameID={oldname.nameID}, platformID={oldname.platformID}, "
f"platEncID={oldname.platEncID}, langID={oldname.langID}")
if newname.string != oldname.string:
fail("Name mismatch for "
f"nameID={oldname.nameID}, platformID={oldname.platformID}, "
f"platEncID={oldname.platEncID}, langID={oldname.langID}")
if ttold['OS/2'].achVendID != ttnew['OS/2'].achVendID:
fail("Vendor ID mismatch")
if failed:
exit(1)