File: compatcheck

package info (click to toggle)
bedstead 3.252-1
  • links: PTS, VCS
  • area: non-free
  • in suites: forky, sid
  • size: 560 kB
  • sloc: ansic: 4,373; python: 337; makefile: 133; sh: 71
file content (134 lines) | stat: -rwxr-xr-x 5,338 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#! /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.
#
# Some 'GSUB' lookups.  For some features, the OpenType specification
# says that the various outputs of an alternate lookup should match
# across fonts in a family.  This suggests that it's reasonable to
# depend on the behaviour of particular inputs to these lookups, and
# that they should thus be consistent within major versions.  Unlike
# for most features above, we'd like to check that the semantics of
# the chosen glyphs haven't changed.  This is made tricky by the fact
# the Bedstead 3.251 changed some glyph names while keeping the same
# semantics, and also changed the shape of some glyphs.  Our approach
# is to look up both old and new glyph names in the new font and check
# that they have the same outline there.  The only feature currently
# handled by this is 'aalt' (Access All Alternates).

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")

def feat_to_dict(gsub, tag):
    # Assertions in this function trap various unhandled cases.
    feature_records = [f for f in gsub.table.FeatureList.FeatureRecord
                       if f.FeatureTag == tag]
    assert(len(feature_records) == 1)
    for fr in feature_records:
        assert(len(fr.Feature.LookupListIndex) == 1)
        for llix in fr.Feature.LookupListIndex:
            lookup = gsub.table.LookupList.Lookup[llix]
            assert(lookup.LookupType == 3)
            assert(len(lookup.SubTable) == 1)
            for st in lookup.SubTable:
                return st.alternates

def charstring(glyphname):
    return ttnew['CFF '].cff[0].CharStrings[glyphname].bytecode

for feat in ['aalt']:
    oldalt = feat_to_dict(ttold['GSUB'], feat)
    newalt = feat_to_dict(ttnew['GSUB'], feat)
    for k in sorted(set(oldalt.keys()) & set(newalt.keys())):
        if ([charstring(x) for x in newalt[k][:len(oldalt[k])]] !=
            [charstring(x) for x in oldalt[k]]):
            fail(f"new '{feat}' lookup for {k} is not a prefix of old one")

if failed:
    exit(1)