xref: /aosp_15_r20/external/fonttools/Lib/fontTools/tfmLib.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes"""Module for reading TFM (TeX Font Metrics) files.
2*e1fe3e4aSElliott Hughes
3*e1fe3e4aSElliott HughesThe TFM format is described in the TFtoPL WEB source code, whose typeset form
4*e1fe3e4aSElliott Hughescan be found on `CTAN <http://mirrors.ctan.org/info/knuth-pdf/texware/tftopl.pdf>`_.
5*e1fe3e4aSElliott Hughes
6*e1fe3e4aSElliott Hughes	>>> from fontTools.tfmLib import TFM
7*e1fe3e4aSElliott Hughes	>>> tfm = TFM("Tests/tfmLib/data/cmr10.tfm")
8*e1fe3e4aSElliott Hughes	>>>
9*e1fe3e4aSElliott Hughes	>>> # Accessing an attribute gets you metadata.
10*e1fe3e4aSElliott Hughes	>>> tfm.checksum
11*e1fe3e4aSElliott Hughes	1274110073
12*e1fe3e4aSElliott Hughes	>>> tfm.designsize
13*e1fe3e4aSElliott Hughes	10.0
14*e1fe3e4aSElliott Hughes	>>> tfm.codingscheme
15*e1fe3e4aSElliott Hughes	'TeX text'
16*e1fe3e4aSElliott Hughes	>>> tfm.family
17*e1fe3e4aSElliott Hughes	'CMR'
18*e1fe3e4aSElliott Hughes	>>> tfm.seven_bit_safe_flag
19*e1fe3e4aSElliott Hughes	False
20*e1fe3e4aSElliott Hughes	>>> tfm.face
21*e1fe3e4aSElliott Hughes	234
22*e1fe3e4aSElliott Hughes	>>> tfm.extraheader
23*e1fe3e4aSElliott Hughes	{}
24*e1fe3e4aSElliott Hughes	>>> tfm.fontdimens
25*e1fe3e4aSElliott Hughes	{'SLANT': 0.0, 'SPACE': 0.33333396911621094, 'STRETCH': 0.16666698455810547, 'SHRINK': 0.11111164093017578, 'XHEIGHT': 0.4305553436279297, 'QUAD': 1.0000028610229492, 'EXTRASPACE': 0.11111164093017578}
26*e1fe3e4aSElliott Hughes	>>> # Accessing a character gets you its metrics.
27*e1fe3e4aSElliott Hughes	>>> # “width” is always available, other metrics are available only when
28*e1fe3e4aSElliott Hughes	>>> # applicable. All values are relative to “designsize”.
29*e1fe3e4aSElliott Hughes	>>> tfm.chars[ord("g")]
30*e1fe3e4aSElliott Hughes	{'width': 0.5000019073486328, 'height': 0.4305553436279297, 'depth': 0.1944446563720703, 'italic': 0.013888359069824219}
31*e1fe3e4aSElliott Hughes	>>> # Kerning and ligature can be accessed as well.
32*e1fe3e4aSElliott Hughes	>>> tfm.kerning[ord("c")]
33*e1fe3e4aSElliott Hughes	{104: -0.02777862548828125, 107: -0.02777862548828125}
34*e1fe3e4aSElliott Hughes	>>> tfm.ligatures[ord("f")]
35*e1fe3e4aSElliott Hughes	{105: ('LIG', 12), 102: ('LIG', 11), 108: ('LIG', 13)}
36*e1fe3e4aSElliott Hughes"""
37*e1fe3e4aSElliott Hughes
38*e1fe3e4aSElliott Hughesfrom types import SimpleNamespace
39*e1fe3e4aSElliott Hughes
40*e1fe3e4aSElliott Hughesfrom fontTools.misc.sstruct import calcsize, unpack, unpack2
41*e1fe3e4aSElliott Hughes
42*e1fe3e4aSElliott HughesSIZES_FORMAT = """
43*e1fe3e4aSElliott Hughes    >
44*e1fe3e4aSElliott Hughes    lf: h    # length of the entire file, in words
45*e1fe3e4aSElliott Hughes    lh: h    # length of the header data, in words
46*e1fe3e4aSElliott Hughes    bc: h    # smallest character code in the font
47*e1fe3e4aSElliott Hughes    ec: h    # largest character code in the font
48*e1fe3e4aSElliott Hughes    nw: h    # number of words in the width table
49*e1fe3e4aSElliott Hughes    nh: h    # number of words in the height table
50*e1fe3e4aSElliott Hughes    nd: h    # number of words in the depth table
51*e1fe3e4aSElliott Hughes    ni: h    # number of words in the italic correction table
52*e1fe3e4aSElliott Hughes    nl: h    # number of words in the ligature/kern table
53*e1fe3e4aSElliott Hughes    nk: h    # number of words in the kern table
54*e1fe3e4aSElliott Hughes    ne: h    # number of words in the extensible character table
55*e1fe3e4aSElliott Hughes    np: h    # number of font parameter words
56*e1fe3e4aSElliott Hughes"""
57*e1fe3e4aSElliott Hughes
58*e1fe3e4aSElliott HughesSIZES_SIZE = calcsize(SIZES_FORMAT)
59*e1fe3e4aSElliott Hughes
60*e1fe3e4aSElliott HughesFIXED_FORMAT = "12.20F"
61*e1fe3e4aSElliott Hughes
62*e1fe3e4aSElliott HughesHEADER_FORMAT1 = f"""
63*e1fe3e4aSElliott Hughes    >
64*e1fe3e4aSElliott Hughes    checksum:            L
65*e1fe3e4aSElliott Hughes    designsize:          {FIXED_FORMAT}
66*e1fe3e4aSElliott Hughes"""
67*e1fe3e4aSElliott Hughes
68*e1fe3e4aSElliott HughesHEADER_FORMAT2 = f"""
69*e1fe3e4aSElliott Hughes    {HEADER_FORMAT1}
70*e1fe3e4aSElliott Hughes    codingscheme:        40p
71*e1fe3e4aSElliott Hughes"""
72*e1fe3e4aSElliott Hughes
73*e1fe3e4aSElliott HughesHEADER_FORMAT3 = f"""
74*e1fe3e4aSElliott Hughes    {HEADER_FORMAT2}
75*e1fe3e4aSElliott Hughes    family:              20p
76*e1fe3e4aSElliott Hughes"""
77*e1fe3e4aSElliott Hughes
78*e1fe3e4aSElliott HughesHEADER_FORMAT4 = f"""
79*e1fe3e4aSElliott Hughes    {HEADER_FORMAT3}
80*e1fe3e4aSElliott Hughes    seven_bit_safe_flag: ?
81*e1fe3e4aSElliott Hughes    ignored:             x
82*e1fe3e4aSElliott Hughes    ignored:             x
83*e1fe3e4aSElliott Hughes    face:                B
84*e1fe3e4aSElliott Hughes"""
85*e1fe3e4aSElliott Hughes
86*e1fe3e4aSElliott HughesHEADER_SIZE1 = calcsize(HEADER_FORMAT1)
87*e1fe3e4aSElliott HughesHEADER_SIZE2 = calcsize(HEADER_FORMAT2)
88*e1fe3e4aSElliott HughesHEADER_SIZE3 = calcsize(HEADER_FORMAT3)
89*e1fe3e4aSElliott HughesHEADER_SIZE4 = calcsize(HEADER_FORMAT4)
90*e1fe3e4aSElliott Hughes
91*e1fe3e4aSElliott HughesLIG_KERN_COMMAND = """
92*e1fe3e4aSElliott Hughes    >
93*e1fe3e4aSElliott Hughes    skip_byte: B
94*e1fe3e4aSElliott Hughes    next_char: B
95*e1fe3e4aSElliott Hughes    op_byte: B
96*e1fe3e4aSElliott Hughes    remainder: B
97*e1fe3e4aSElliott Hughes"""
98*e1fe3e4aSElliott Hughes
99*e1fe3e4aSElliott HughesBASE_PARAMS = [
100*e1fe3e4aSElliott Hughes    "SLANT",
101*e1fe3e4aSElliott Hughes    "SPACE",
102*e1fe3e4aSElliott Hughes    "STRETCH",
103*e1fe3e4aSElliott Hughes    "SHRINK",
104*e1fe3e4aSElliott Hughes    "XHEIGHT",
105*e1fe3e4aSElliott Hughes    "QUAD",
106*e1fe3e4aSElliott Hughes    "EXTRASPACE",
107*e1fe3e4aSElliott Hughes]
108*e1fe3e4aSElliott Hughes
109*e1fe3e4aSElliott HughesMATHSY_PARAMS = [
110*e1fe3e4aSElliott Hughes    "NUM1",
111*e1fe3e4aSElliott Hughes    "NUM2",
112*e1fe3e4aSElliott Hughes    "NUM3",
113*e1fe3e4aSElliott Hughes    "DENOM1",
114*e1fe3e4aSElliott Hughes    "DENOM2",
115*e1fe3e4aSElliott Hughes    "SUP1",
116*e1fe3e4aSElliott Hughes    "SUP2",
117*e1fe3e4aSElliott Hughes    "SUP3",
118*e1fe3e4aSElliott Hughes    "SUB1",
119*e1fe3e4aSElliott Hughes    "SUB2",
120*e1fe3e4aSElliott Hughes    "SUPDROP",
121*e1fe3e4aSElliott Hughes    "SUBDROP",
122*e1fe3e4aSElliott Hughes    "DELIM1",
123*e1fe3e4aSElliott Hughes    "DELIM2",
124*e1fe3e4aSElliott Hughes    "AXISHEIGHT",
125*e1fe3e4aSElliott Hughes]
126*e1fe3e4aSElliott Hughes
127*e1fe3e4aSElliott HughesMATHEX_PARAMS = [
128*e1fe3e4aSElliott Hughes    "DEFAULTRULETHICKNESS",
129*e1fe3e4aSElliott Hughes    "BIGOPSPACING1",
130*e1fe3e4aSElliott Hughes    "BIGOPSPACING2",
131*e1fe3e4aSElliott Hughes    "BIGOPSPACING3",
132*e1fe3e4aSElliott Hughes    "BIGOPSPACING4",
133*e1fe3e4aSElliott Hughes    "BIGOPSPACING5",
134*e1fe3e4aSElliott Hughes]
135*e1fe3e4aSElliott Hughes
136*e1fe3e4aSElliott HughesVANILLA = 0
137*e1fe3e4aSElliott HughesMATHSY = 1
138*e1fe3e4aSElliott HughesMATHEX = 2
139*e1fe3e4aSElliott Hughes
140*e1fe3e4aSElliott HughesUNREACHABLE = 0
141*e1fe3e4aSElliott HughesPASSTHROUGH = 1
142*e1fe3e4aSElliott HughesACCESSABLE = 2
143*e1fe3e4aSElliott Hughes
144*e1fe3e4aSElliott HughesNO_TAG = 0
145*e1fe3e4aSElliott HughesLIG_TAG = 1
146*e1fe3e4aSElliott HughesLIST_TAG = 2
147*e1fe3e4aSElliott HughesEXT_TAG = 3
148*e1fe3e4aSElliott Hughes
149*e1fe3e4aSElliott HughesSTOP_FLAG = 128
150*e1fe3e4aSElliott HughesKERN_FLAG = 128
151*e1fe3e4aSElliott Hughes
152*e1fe3e4aSElliott Hughes
153*e1fe3e4aSElliott Hughesclass TFMException(Exception):
154*e1fe3e4aSElliott Hughes    def __init__(self, message):
155*e1fe3e4aSElliott Hughes        super().__init__(message)
156*e1fe3e4aSElliott Hughes
157*e1fe3e4aSElliott Hughes
158*e1fe3e4aSElliott Hughesclass TFM:
159*e1fe3e4aSElliott Hughes    def __init__(self, file):
160*e1fe3e4aSElliott Hughes        self._read(file)
161*e1fe3e4aSElliott Hughes
162*e1fe3e4aSElliott Hughes    def __repr__(self):
163*e1fe3e4aSElliott Hughes        return (
164*e1fe3e4aSElliott Hughes            f"<TFM"
165*e1fe3e4aSElliott Hughes            f" for {self.family}"
166*e1fe3e4aSElliott Hughes            f" in {self.codingscheme}"
167*e1fe3e4aSElliott Hughes            f" at {self.designsize:g}pt>"
168*e1fe3e4aSElliott Hughes        )
169*e1fe3e4aSElliott Hughes
170*e1fe3e4aSElliott Hughes    def _read(self, file):
171*e1fe3e4aSElliott Hughes        if hasattr(file, "read"):
172*e1fe3e4aSElliott Hughes            data = file.read()
173*e1fe3e4aSElliott Hughes        else:
174*e1fe3e4aSElliott Hughes            with open(file, "rb") as fp:
175*e1fe3e4aSElliott Hughes                data = fp.read()
176*e1fe3e4aSElliott Hughes
177*e1fe3e4aSElliott Hughes        self._data = data
178*e1fe3e4aSElliott Hughes
179*e1fe3e4aSElliott Hughes        if len(data) < SIZES_SIZE:
180*e1fe3e4aSElliott Hughes            raise TFMException("Too short input file")
181*e1fe3e4aSElliott Hughes
182*e1fe3e4aSElliott Hughes        sizes = SimpleNamespace()
183*e1fe3e4aSElliott Hughes        unpack2(SIZES_FORMAT, data, sizes)
184*e1fe3e4aSElliott Hughes
185*e1fe3e4aSElliott Hughes        # Do some file structure sanity checks.
186*e1fe3e4aSElliott Hughes        # TeX and TFtoPL do additional functional checks and might even correct
187*e1fe3e4aSElliott Hughes        # “errors” in the input file, but we instead try to output the file as
188*e1fe3e4aSElliott Hughes        # it is as long as it is parsable, even if the data make no sense.
189*e1fe3e4aSElliott Hughes
190*e1fe3e4aSElliott Hughes        if sizes.lf < 0:
191*e1fe3e4aSElliott Hughes            raise TFMException("The file claims to have negative or zero length!")
192*e1fe3e4aSElliott Hughes
193*e1fe3e4aSElliott Hughes        if len(data) < sizes.lf * 4:
194*e1fe3e4aSElliott Hughes            raise TFMException("The file has fewer bytes than it claims!")
195*e1fe3e4aSElliott Hughes
196*e1fe3e4aSElliott Hughes        for name, length in vars(sizes).items():
197*e1fe3e4aSElliott Hughes            if length < 0:
198*e1fe3e4aSElliott Hughes                raise TFMException("The subfile size: '{name}' is negative!")
199*e1fe3e4aSElliott Hughes
200*e1fe3e4aSElliott Hughes        if sizes.lh < 2:
201*e1fe3e4aSElliott Hughes            raise TFMException(f"The header length is only {sizes.lh}!")
202*e1fe3e4aSElliott Hughes
203*e1fe3e4aSElliott Hughes        if sizes.bc > sizes.ec + 1 or sizes.ec > 255:
204*e1fe3e4aSElliott Hughes            raise TFMException(
205*e1fe3e4aSElliott Hughes                f"The character code range {sizes.bc}..{sizes.ec} is illegal!"
206*e1fe3e4aSElliott Hughes            )
207*e1fe3e4aSElliott Hughes
208*e1fe3e4aSElliott Hughes        if sizes.nw == 0 or sizes.nh == 0 or sizes.nd == 0 or sizes.ni == 0:
209*e1fe3e4aSElliott Hughes            raise TFMException("Incomplete subfiles for character dimensions!")
210*e1fe3e4aSElliott Hughes
211*e1fe3e4aSElliott Hughes        if sizes.ne > 256:
212*e1fe3e4aSElliott Hughes            raise TFMException(f"There are {ne} extensible recipes!")
213*e1fe3e4aSElliott Hughes
214*e1fe3e4aSElliott Hughes        if sizes.lf != (
215*e1fe3e4aSElliott Hughes            6
216*e1fe3e4aSElliott Hughes            + sizes.lh
217*e1fe3e4aSElliott Hughes            + (sizes.ec - sizes.bc + 1)
218*e1fe3e4aSElliott Hughes            + sizes.nw
219*e1fe3e4aSElliott Hughes            + sizes.nh
220*e1fe3e4aSElliott Hughes            + sizes.nd
221*e1fe3e4aSElliott Hughes            + sizes.ni
222*e1fe3e4aSElliott Hughes            + sizes.nl
223*e1fe3e4aSElliott Hughes            + sizes.nk
224*e1fe3e4aSElliott Hughes            + sizes.ne
225*e1fe3e4aSElliott Hughes            + sizes.np
226*e1fe3e4aSElliott Hughes        ):
227*e1fe3e4aSElliott Hughes            raise TFMException("Subfile sizes don’t add up to the stated total")
228*e1fe3e4aSElliott Hughes
229*e1fe3e4aSElliott Hughes        # Subfile offsets, used in the helper function below. These all are
230*e1fe3e4aSElliott Hughes        # 32-bit word offsets not 8-bit byte offsets.
231*e1fe3e4aSElliott Hughes        char_base = 6 + sizes.lh - sizes.bc
232*e1fe3e4aSElliott Hughes        width_base = char_base + sizes.ec + 1
233*e1fe3e4aSElliott Hughes        height_base = width_base + sizes.nw
234*e1fe3e4aSElliott Hughes        depth_base = height_base + sizes.nh
235*e1fe3e4aSElliott Hughes        italic_base = depth_base + sizes.nd
236*e1fe3e4aSElliott Hughes        lig_kern_base = italic_base + sizes.ni
237*e1fe3e4aSElliott Hughes        kern_base = lig_kern_base + sizes.nl
238*e1fe3e4aSElliott Hughes        exten_base = kern_base + sizes.nk
239*e1fe3e4aSElliott Hughes        param_base = exten_base + sizes.ne
240*e1fe3e4aSElliott Hughes
241*e1fe3e4aSElliott Hughes        # Helper functions for accessing individual data. If this looks
242*e1fe3e4aSElliott Hughes        # nonidiomatic Python, I blame the effect of reading the literate WEB
243*e1fe3e4aSElliott Hughes        # documentation of TFtoPL.
244*e1fe3e4aSElliott Hughes        def char_info(c):
245*e1fe3e4aSElliott Hughes            return 4 * (char_base + c)
246*e1fe3e4aSElliott Hughes
247*e1fe3e4aSElliott Hughes        def width_index(c):
248*e1fe3e4aSElliott Hughes            return data[char_info(c)]
249*e1fe3e4aSElliott Hughes
250*e1fe3e4aSElliott Hughes        def noneexistent(c):
251*e1fe3e4aSElliott Hughes            return c < sizes.bc or c > sizes.ec or width_index(c) == 0
252*e1fe3e4aSElliott Hughes
253*e1fe3e4aSElliott Hughes        def height_index(c):
254*e1fe3e4aSElliott Hughes            return data[char_info(c) + 1] // 16
255*e1fe3e4aSElliott Hughes
256*e1fe3e4aSElliott Hughes        def depth_index(c):
257*e1fe3e4aSElliott Hughes            return data[char_info(c) + 1] % 16
258*e1fe3e4aSElliott Hughes
259*e1fe3e4aSElliott Hughes        def italic_index(c):
260*e1fe3e4aSElliott Hughes            return data[char_info(c) + 2] // 4
261*e1fe3e4aSElliott Hughes
262*e1fe3e4aSElliott Hughes        def tag(c):
263*e1fe3e4aSElliott Hughes            return data[char_info(c) + 2] % 4
264*e1fe3e4aSElliott Hughes
265*e1fe3e4aSElliott Hughes        def remainder(c):
266*e1fe3e4aSElliott Hughes            return data[char_info(c) + 3]
267*e1fe3e4aSElliott Hughes
268*e1fe3e4aSElliott Hughes        def width(c):
269*e1fe3e4aSElliott Hughes            r = 4 * (width_base + width_index(c))
270*e1fe3e4aSElliott Hughes            return read_fixed(r, "v")["v"]
271*e1fe3e4aSElliott Hughes
272*e1fe3e4aSElliott Hughes        def height(c):
273*e1fe3e4aSElliott Hughes            r = 4 * (height_base + height_index(c))
274*e1fe3e4aSElliott Hughes            return read_fixed(r, "v")["v"]
275*e1fe3e4aSElliott Hughes
276*e1fe3e4aSElliott Hughes        def depth(c):
277*e1fe3e4aSElliott Hughes            r = 4 * (depth_base + depth_index(c))
278*e1fe3e4aSElliott Hughes            return read_fixed(r, "v")["v"]
279*e1fe3e4aSElliott Hughes
280*e1fe3e4aSElliott Hughes        def italic(c):
281*e1fe3e4aSElliott Hughes            r = 4 * (italic_base + italic_index(c))
282*e1fe3e4aSElliott Hughes            return read_fixed(r, "v")["v"]
283*e1fe3e4aSElliott Hughes
284*e1fe3e4aSElliott Hughes        def exten(c):
285*e1fe3e4aSElliott Hughes            return 4 * (exten_base + remainder(c))
286*e1fe3e4aSElliott Hughes
287*e1fe3e4aSElliott Hughes        def lig_step(i):
288*e1fe3e4aSElliott Hughes            return 4 * (lig_kern_base + i)
289*e1fe3e4aSElliott Hughes
290*e1fe3e4aSElliott Hughes        def lig_kern_command(i):
291*e1fe3e4aSElliott Hughes            command = SimpleNamespace()
292*e1fe3e4aSElliott Hughes            unpack2(LIG_KERN_COMMAND, data[i:], command)
293*e1fe3e4aSElliott Hughes            return command
294*e1fe3e4aSElliott Hughes
295*e1fe3e4aSElliott Hughes        def kern(i):
296*e1fe3e4aSElliott Hughes            r = 4 * (kern_base + i)
297*e1fe3e4aSElliott Hughes            return read_fixed(r, "v")["v"]
298*e1fe3e4aSElliott Hughes
299*e1fe3e4aSElliott Hughes        def param(i):
300*e1fe3e4aSElliott Hughes            return 4 * (param_base + i)
301*e1fe3e4aSElliott Hughes
302*e1fe3e4aSElliott Hughes        def read_fixed(index, key, obj=None):
303*e1fe3e4aSElliott Hughes            ret = unpack2(f">;{key}:{FIXED_FORMAT}", data[index:], obj)
304*e1fe3e4aSElliott Hughes            return ret[0]
305*e1fe3e4aSElliott Hughes
306*e1fe3e4aSElliott Hughes        # Set all attributes to empty values regardless of the header size.
307*e1fe3e4aSElliott Hughes        unpack(HEADER_FORMAT4, [0] * HEADER_SIZE4, self)
308*e1fe3e4aSElliott Hughes
309*e1fe3e4aSElliott Hughes        offset = 24
310*e1fe3e4aSElliott Hughes        length = sizes.lh * 4
311*e1fe3e4aSElliott Hughes        self.extraheader = {}
312*e1fe3e4aSElliott Hughes        if length >= HEADER_SIZE4:
313*e1fe3e4aSElliott Hughes            rest = unpack2(HEADER_FORMAT4, data[offset:], self)[1]
314*e1fe3e4aSElliott Hughes            if self.face < 18:
315*e1fe3e4aSElliott Hughes                s = self.face % 2
316*e1fe3e4aSElliott Hughes                b = self.face // 2
317*e1fe3e4aSElliott Hughes                self.face = "MBL"[b % 3] + "RI"[s] + "RCE"[b // 3]
318*e1fe3e4aSElliott Hughes            for i in range(sizes.lh - HEADER_SIZE4 // 4):
319*e1fe3e4aSElliott Hughes                rest = unpack2(f">;HEADER{i + 18}:l", rest, self.extraheader)[1]
320*e1fe3e4aSElliott Hughes        elif length >= HEADER_SIZE3:
321*e1fe3e4aSElliott Hughes            unpack2(HEADER_FORMAT3, data[offset:], self)
322*e1fe3e4aSElliott Hughes        elif length >= HEADER_SIZE2:
323*e1fe3e4aSElliott Hughes            unpack2(HEADER_FORMAT2, data[offset:], self)
324*e1fe3e4aSElliott Hughes        elif length >= HEADER_SIZE1:
325*e1fe3e4aSElliott Hughes            unpack2(HEADER_FORMAT1, data[offset:], self)
326*e1fe3e4aSElliott Hughes
327*e1fe3e4aSElliott Hughes        self.fonttype = VANILLA
328*e1fe3e4aSElliott Hughes        scheme = self.codingscheme.upper()
329*e1fe3e4aSElliott Hughes        if scheme.startswith("TEX MATH SY"):
330*e1fe3e4aSElliott Hughes            self.fonttype = MATHSY
331*e1fe3e4aSElliott Hughes        elif scheme.startswith("TEX MATH EX"):
332*e1fe3e4aSElliott Hughes            self.fonttype = MATHEX
333*e1fe3e4aSElliott Hughes
334*e1fe3e4aSElliott Hughes        self.fontdimens = {}
335*e1fe3e4aSElliott Hughes        for i in range(sizes.np):
336*e1fe3e4aSElliott Hughes            name = f"PARAMETER{i+1}"
337*e1fe3e4aSElliott Hughes            if i <= 6:
338*e1fe3e4aSElliott Hughes                name = BASE_PARAMS[i]
339*e1fe3e4aSElliott Hughes            elif self.fonttype == MATHSY and i <= 21:
340*e1fe3e4aSElliott Hughes                name = MATHSY_PARAMS[i - 7]
341*e1fe3e4aSElliott Hughes            elif self.fonttype == MATHEX and i <= 12:
342*e1fe3e4aSElliott Hughes                name = MATHEX_PARAMS[i - 7]
343*e1fe3e4aSElliott Hughes            read_fixed(param(i), name, self.fontdimens)
344*e1fe3e4aSElliott Hughes
345*e1fe3e4aSElliott Hughes        lig_kern_map = {}
346*e1fe3e4aSElliott Hughes        self.right_boundary_char = None
347*e1fe3e4aSElliott Hughes        self.left_boundary_char = None
348*e1fe3e4aSElliott Hughes        if sizes.nl > 0:
349*e1fe3e4aSElliott Hughes            cmd = lig_kern_command(lig_step(0))
350*e1fe3e4aSElliott Hughes            if cmd.skip_byte == 255:
351*e1fe3e4aSElliott Hughes                self.right_boundary_char = cmd.next_char
352*e1fe3e4aSElliott Hughes
353*e1fe3e4aSElliott Hughes            cmd = lig_kern_command(lig_step((sizes.nl - 1)))
354*e1fe3e4aSElliott Hughes            if cmd.skip_byte == 255:
355*e1fe3e4aSElliott Hughes                self.left_boundary_char = 256
356*e1fe3e4aSElliott Hughes                r = 256 * cmd.op_byte + cmd.remainder
357*e1fe3e4aSElliott Hughes                lig_kern_map[self.left_boundary_char] = r
358*e1fe3e4aSElliott Hughes
359*e1fe3e4aSElliott Hughes        self.chars = {}
360*e1fe3e4aSElliott Hughes        for c in range(sizes.bc, sizes.ec + 1):
361*e1fe3e4aSElliott Hughes            if width_index(c) > 0:
362*e1fe3e4aSElliott Hughes                self.chars[c] = info = {}
363*e1fe3e4aSElliott Hughes                info["width"] = width(c)
364*e1fe3e4aSElliott Hughes                if height_index(c) > 0:
365*e1fe3e4aSElliott Hughes                    info["height"] = height(c)
366*e1fe3e4aSElliott Hughes                if depth_index(c) > 0:
367*e1fe3e4aSElliott Hughes                    info["depth"] = depth(c)
368*e1fe3e4aSElliott Hughes                if italic_index(c) > 0:
369*e1fe3e4aSElliott Hughes                    info["italic"] = italic(c)
370*e1fe3e4aSElliott Hughes                char_tag = tag(c)
371*e1fe3e4aSElliott Hughes                if char_tag == NO_TAG:
372*e1fe3e4aSElliott Hughes                    pass
373*e1fe3e4aSElliott Hughes                elif char_tag == LIG_TAG:
374*e1fe3e4aSElliott Hughes                    lig_kern_map[c] = remainder(c)
375*e1fe3e4aSElliott Hughes                elif char_tag == LIST_TAG:
376*e1fe3e4aSElliott Hughes                    info["nextlarger"] = remainder(c)
377*e1fe3e4aSElliott Hughes                elif char_tag == EXT_TAG:
378*e1fe3e4aSElliott Hughes                    info["varchar"] = varchar = {}
379*e1fe3e4aSElliott Hughes                    for i in range(4):
380*e1fe3e4aSElliott Hughes                        part = data[exten(c) + i]
381*e1fe3e4aSElliott Hughes                        if i == 3 or part > 0:
382*e1fe3e4aSElliott Hughes                            name = "rep"
383*e1fe3e4aSElliott Hughes                            if i == 0:
384*e1fe3e4aSElliott Hughes                                name = "top"
385*e1fe3e4aSElliott Hughes                            elif i == 1:
386*e1fe3e4aSElliott Hughes                                name = "mid"
387*e1fe3e4aSElliott Hughes                            elif i == 2:
388*e1fe3e4aSElliott Hughes                                name = "bot"
389*e1fe3e4aSElliott Hughes                            if noneexistent(part):
390*e1fe3e4aSElliott Hughes                                varchar[name] = c
391*e1fe3e4aSElliott Hughes                            else:
392*e1fe3e4aSElliott Hughes                                varchar[name] = part
393*e1fe3e4aSElliott Hughes
394*e1fe3e4aSElliott Hughes        self.ligatures = {}
395*e1fe3e4aSElliott Hughes        self.kerning = {}
396*e1fe3e4aSElliott Hughes        for c, i in sorted(lig_kern_map.items()):
397*e1fe3e4aSElliott Hughes            cmd = lig_kern_command(lig_step(i))
398*e1fe3e4aSElliott Hughes            if cmd.skip_byte > STOP_FLAG:
399*e1fe3e4aSElliott Hughes                i = 256 * cmd.op_byte + cmd.remainder
400*e1fe3e4aSElliott Hughes
401*e1fe3e4aSElliott Hughes            while i < sizes.nl:
402*e1fe3e4aSElliott Hughes                cmd = lig_kern_command(lig_step(i))
403*e1fe3e4aSElliott Hughes                if cmd.skip_byte > STOP_FLAG:
404*e1fe3e4aSElliott Hughes                    pass
405*e1fe3e4aSElliott Hughes                else:
406*e1fe3e4aSElliott Hughes                    if cmd.op_byte >= KERN_FLAG:
407*e1fe3e4aSElliott Hughes                        r = 256 * (cmd.op_byte - KERN_FLAG) + cmd.remainder
408*e1fe3e4aSElliott Hughes                        self.kerning.setdefault(c, {})[cmd.next_char] = kern(r)
409*e1fe3e4aSElliott Hughes                    else:
410*e1fe3e4aSElliott Hughes                        r = cmd.op_byte
411*e1fe3e4aSElliott Hughes                        if r == 4 or (r > 7 and r != 11):
412*e1fe3e4aSElliott Hughes                            # Ligature step with nonstandard code, we output
413*e1fe3e4aSElliott Hughes                            # the code verbatim.
414*e1fe3e4aSElliott Hughes                            lig = r
415*e1fe3e4aSElliott Hughes                        else:
416*e1fe3e4aSElliott Hughes                            lig = ""
417*e1fe3e4aSElliott Hughes                            if r % 4 > 1:
418*e1fe3e4aSElliott Hughes                                lig += "/"
419*e1fe3e4aSElliott Hughes                            lig += "LIG"
420*e1fe3e4aSElliott Hughes                            if r % 2 != 0:
421*e1fe3e4aSElliott Hughes                                lig += "/"
422*e1fe3e4aSElliott Hughes                            while r > 3:
423*e1fe3e4aSElliott Hughes                                lig += ">"
424*e1fe3e4aSElliott Hughes                                r -= 4
425*e1fe3e4aSElliott Hughes                        self.ligatures.setdefault(c, {})[cmd.next_char] = (
426*e1fe3e4aSElliott Hughes                            lig,
427*e1fe3e4aSElliott Hughes                            cmd.remainder,
428*e1fe3e4aSElliott Hughes                        )
429*e1fe3e4aSElliott Hughes
430*e1fe3e4aSElliott Hughes                if cmd.skip_byte >= STOP_FLAG:
431*e1fe3e4aSElliott Hughes                    break
432*e1fe3e4aSElliott Hughes                i += cmd.skip_byte + 1
433*e1fe3e4aSElliott Hughes
434*e1fe3e4aSElliott Hughes
435*e1fe3e4aSElliott Hughesif __name__ == "__main__":
436*e1fe3e4aSElliott Hughes    import sys
437*e1fe3e4aSElliott Hughes
438*e1fe3e4aSElliott Hughes    tfm = TFM(sys.argv[1])
439*e1fe3e4aSElliott Hughes    print(
440*e1fe3e4aSElliott Hughes        "\n".join(
441*e1fe3e4aSElliott Hughes            x
442*e1fe3e4aSElliott Hughes            for x in [
443*e1fe3e4aSElliott Hughes                f"tfm.checksum={tfm.checksum}",
444*e1fe3e4aSElliott Hughes                f"tfm.designsize={tfm.designsize}",
445*e1fe3e4aSElliott Hughes                f"tfm.codingscheme={tfm.codingscheme}",
446*e1fe3e4aSElliott Hughes                f"tfm.fonttype={tfm.fonttype}",
447*e1fe3e4aSElliott Hughes                f"tfm.family={tfm.family}",
448*e1fe3e4aSElliott Hughes                f"tfm.seven_bit_safe_flag={tfm.seven_bit_safe_flag}",
449*e1fe3e4aSElliott Hughes                f"tfm.face={tfm.face}",
450*e1fe3e4aSElliott Hughes                f"tfm.extraheader={tfm.extraheader}",
451*e1fe3e4aSElliott Hughes                f"tfm.fontdimens={tfm.fontdimens}",
452*e1fe3e4aSElliott Hughes                f"tfm.right_boundary_char={tfm.right_boundary_char}",
453*e1fe3e4aSElliott Hughes                f"tfm.left_boundary_char={tfm.left_boundary_char}",
454*e1fe3e4aSElliott Hughes                f"tfm.kerning={tfm.kerning}",
455*e1fe3e4aSElliott Hughes                f"tfm.ligatures={tfm.ligatures}",
456*e1fe3e4aSElliott Hughes                f"tfm.chars={tfm.chars}",
457*e1fe3e4aSElliott Hughes            ]
458*e1fe3e4aSElliott Hughes        )
459*e1fe3e4aSElliott Hughes    )
460*e1fe3e4aSElliott Hughes    print(tfm)
461