5
5
# found in the LICENSE.md file.
6
6
"""Extract metadata from OpenType fonts."""
7
7
8
- from fontTools .ttLib import TTFont
9
- from fontTools .ttLib .tables import otTables
10
8
import functools
11
9
import logging
10
+ from typing import Optional
11
+
12
+ from fontTools .ttLib import TTFont
13
+ from fontTools .ttLib .tables import otTables
12
14
13
15
14
16
_NAME_ID_VERSION = 5
@@ -27,80 +29,96 @@ def _safe_map(m):
27
29
return {k : v for k , v in m .items () if _safe_result_type (v )}
28
30
29
31
32
+ def _read_head (font : TTFont ) -> Optional [dict ]:
33
+ try :
34
+ return _safe_map (font ["head" ].__dict__ )
35
+ except Exception :
36
+ logging .exception ("Error reading font head" )
37
+
38
+
30
39
def _read_names (ttf , name_ids ):
31
40
names = {}
32
41
try :
33
42
# limit # of names we retain
34
43
unicode_names = sorted (
35
- (n for n in ttf ['name' ].names
36
- if n .isUnicode () and n .nameID <= _MAX_NAME_ID ),
37
- key = lambda n : n .nameID
44
+ (
45
+ n
46
+ for n in ttf ["name" ].names
47
+ if n .isUnicode () and n .nameID <= _MAX_NAME_ID
48
+ ),
49
+ key = lambda n : n .nameID ,
38
50
)[:_MAX_NAMES ]
39
51
# limit length of names we retain
40
52
for name in unicode_names :
41
53
try :
42
54
names [name .nameID ] = name .toUnicode ()[:_MAX_NAME_LEN ]
43
55
except Exception :
44
- logging .exception (' Error converting name to unicode' )
56
+ logging .exception (" Error converting name to unicode" )
45
57
46
58
except Exception :
47
- logging .exception (' Error reading font names' )
59
+ logging .exception (" Error reading font names" )
48
60
if not names :
49
61
return None
50
62
return names
51
63
52
64
53
65
def _read_os2 (ttf ):
54
66
try :
55
- os2 = _safe_map (ttf [' OS/2' ].__dict__ )
56
- os2 [' panose' ] = _safe_map (ttf [' OS/2' ].panose .__dict__ )
67
+ os2 = _safe_map (ttf [" OS/2" ].__dict__ )
68
+ os2 [" panose" ] = _safe_map (ttf [" OS/2" ].panose .__dict__ )
57
69
return os2
58
70
except Exception :
59
- logging .exception (' Error reading font OS/2' )
71
+ logging .exception (" Error reading font OS/2" )
60
72
return None
61
73
62
74
63
75
def _read_post (ttf ):
64
76
try :
65
- post = _safe_map (ttf [' post' ].__dict__ )
77
+ post = _safe_map (ttf [" post" ].__dict__ )
66
78
return post
67
79
except Exception :
68
- logging .exception (' Error reading font post' )
80
+ logging .exception (" Error reading font post" )
69
81
return None
70
82
71
83
72
84
def _read_fvar (ttf ):
73
- if ' fvar' in ttf :
85
+ if " fvar" in ttf :
74
86
try :
75
87
return {
76
88
a .axisTag : {
77
- ' min' : a .minValue ,
78
- ' default' : a .defaultValue ,
79
- ' max' : a .maxValue
89
+ " min" : a .minValue ,
90
+ " default" : a .defaultValue ,
91
+ " max" : a .maxValue ,
80
92
}
81
- for a in ttf [' fvar' ].axes
93
+ for a in ttf [" fvar" ].axes
82
94
}
83
95
except Exception :
84
- logging .exception (' Error reading axes' )
96
+ logging .exception (" Error reading axes" )
85
97
return None
86
98
87
99
88
100
def _read_codepoint_glyph_counts (ttf ):
89
101
try :
90
102
glyph_count = len (ttf .getGlyphOrder ())
91
- unicode_cmaps = (t .cmap .keys () for t in ttf ['cmap' ].tables if t .isUnicode ())
92
- unique_codepoints = functools .reduce (lambda acc , u : acc | u , unicode_cmaps , set ())
103
+ unicode_cmaps = (t .cmap .keys () for t in ttf ["cmap" ].tables if t .isUnicode ())
104
+ unique_codepoints = functools .reduce (
105
+ lambda acc , u : acc | u , unicode_cmaps , set ()
106
+ )
93
107
return {
94
- ' num_cmap_codepoints' : len (unique_codepoints ),
95
- ' num_glyphs' : glyph_count
108
+ " num_cmap_codepoints" : len (unique_codepoints ),
109
+ " num_glyphs" : glyph_count ,
96
110
}
97
111
except Exception :
98
- logging .exception (' Error reading codepoint and glyph count' )
112
+ logging .exception (" Error reading codepoint and glyph count" )
99
113
return None
100
114
115
+
101
116
def _read_cmap (ttf ):
102
117
try :
103
- encodings = [{ 'platform' : t .platformID , 'encoding' : t .platEncID } for t in ttf ['cmap' ].tables ]
118
+ encodings = [
119
+ {"platform" : t .platformID , "encoding" : t .platEncID }
120
+ for t in ttf ["cmap" ].tables
121
+ ]
104
122
codepoints = []
105
123
106
124
cmap = ttf .getBestCmap ()
@@ -109,99 +127,112 @@ def _read_cmap(ttf):
109
127
codepoints = [codepoint for codepoint in ttf .getBestCmap ()]
110
128
111
129
return {
112
- ' encodings' : encodings ,
113
- ' codepoints' : codepoints ,
130
+ " encodings" : encodings ,
131
+ " codepoints" : codepoints ,
114
132
}
115
133
except Exception :
116
- logging .exception (' Error reading cmap data' )
134
+ logging .exception (" Error reading cmap data" )
117
135
return None
118
136
137
+
119
138
def _read_color (ttf ):
120
139
try :
121
140
t = []
122
141
123
142
# It is possible a single font uses multiple color
124
143
# formats for wider OS and browser support.
125
- if ' COLR' in ttf and ttf [' COLR' ].version == 0 :
126
- t .append (' COLRv0' )
144
+ if " COLR" in ttf and ttf [" COLR" ].version == 0 :
145
+ t .append (" COLRv0" )
127
146
128
- if ' COLR' in ttf and ttf [' COLR' ].version == 1 :
129
- t .append (' COLRv1' )
147
+ if " COLR" in ttf and ttf [" COLR" ].version == 1 :
148
+ t .append (" COLRv1" )
130
149
131
- if ' SVG ' in ttf :
132
- t .append (' SVG' )
150
+ if " SVG " in ttf :
151
+ t .append (" SVG" )
133
152
134
- if ' CBDT' in ttf :
135
- t .append (' CBDT' )
153
+ if " CBDT" in ttf :
154
+ t .append (" CBDT" )
136
155
137
- if ' sbix' in ttf :
138
- t .append (' sbix' )
156
+ if " sbix" in ttf :
157
+ t .append (" sbix" )
139
158
140
159
numPalettes = 0
141
160
numPaletteEntries = 0
142
161
143
- if ' CPAL' in ttf :
144
- numPaletteEntries = ttf [' CPAL' ].numPaletteEntries
145
- numPalettes = len (ttf [' CPAL' ].palettes )
162
+ if " CPAL" in ttf :
163
+ numPaletteEntries = ttf [" CPAL" ].numPaletteEntries
164
+ numPalettes = len (ttf [" CPAL" ].palettes )
146
165
147
166
return {
148
- ' formats' : t ,
149
- ' numPalettes' : numPalettes ,
150
- ' numPaletteEntries' : numPaletteEntries
167
+ " formats" : t ,
168
+ " numPalettes" : numPalettes ,
169
+ " numPaletteEntries" : numPaletteEntries ,
151
170
}
152
171
except Exception :
153
- logging .exception (' Error reading color font data' )
172
+ logging .exception (" Error reading color font data" )
154
173
return None
155
174
175
+
156
176
def _read_features (ttf ):
157
177
try :
158
178
result = {}
159
179
160
180
# This is loosely based on: https://github.com/fonttools/fonttools/blob/main/Snippets/layout-features.py
161
- for tag in ('GSUB' , 'GPOS' ):
162
- if not tag in ttf : continue
181
+ for tag in ("GSUB" , "GPOS" ):
182
+ if not tag in ttf :
183
+ continue
163
184
table = ttf [tag ].table
164
185
165
186
if not tag in result :
166
187
result [tag ] = {}
167
188
168
- if not table .ScriptList or not table .FeatureList : continue
189
+ if not table .ScriptList or not table .FeatureList :
190
+ continue
169
191
featureRecords = table .FeatureList .FeatureRecord
170
192
for script in table .ScriptList .ScriptRecord :
171
- if not script .Script : continue
193
+ if not script .Script :
194
+ continue
172
195
if not script .ScriptTag in result [tag ]:
173
196
result [tag ][script .ScriptTag ] = {}
174
197
175
198
languages = list (script .Script .LangSysRecord )
176
199
177
200
if script .Script .DefaultLangSys :
201
+ # pylint: disable=no-member
178
202
defaultlangsys = otTables .LangSysRecord ()
179
203
defaultlangsys .LangSysTag = "default"
180
204
defaultlangsys .LangSys = script .Script .DefaultLangSys
181
205
languages .insert (0 , defaultlangsys )
182
206
183
207
for langsys in languages :
184
- if not langsys .LangSys : continue
208
+ if not langsys .LangSys :
209
+ continue
185
210
186
211
if not langsys .LangSysTag in result [tag ][script .ScriptTag ]:
187
212
result [tag ][script .ScriptTag ][langsys .LangSysTag ] = []
188
213
189
- features = [featureRecords [index ] for index in langsys .LangSys .FeatureIndex ]
214
+ features = [
215
+ featureRecords [index ] for index in langsys .LangSys .FeatureIndex
216
+ ]
190
217
191
218
if langsys .LangSys .ReqFeatureIndex != 0xFFFF :
192
219
record = featureRecords [langsys .LangSys .ReqFeatureIndex ]
220
+ # pylint: disable=no-member
193
221
requiredfeature = otTables .FeatureRecord ()
194
- requiredfeature .FeatureTag = ' required(%s)' % record .FeatureTag
222
+ requiredfeature .FeatureTag = " required(%s)" % record .FeatureTag
195
223
requiredfeature .Feature = record .Feature
196
224
features .insert (0 , requiredfeature )
197
225
for feature in features :
198
- result [tag ][script .ScriptTag ][langsys .LangSysTag ].append (feature .FeatureTag )
226
+ result [tag ][script .ScriptTag ][langsys .LangSysTag ].append (
227
+ feature .FeatureTag
228
+ )
199
229
200
230
return result
201
231
except Exception :
202
- logging .exception (' Error reading OpenType feature data' )
232
+ logging .exception (" Error reading OpenType feature data" )
203
233
return None
204
234
235
+
205
236
def read_metadata (font ):
206
237
ttf = TTFont (font , fontNumber = 0 , lazy = True )
207
238
try :
@@ -211,29 +242,34 @@ def read_metadata(font):
211
242
reader = ttf .reader
212
243
213
244
metadata = {
214
- 'table_sizes' : {tag : reader .tables [tag ].length
215
- for tag in sorted (reader .keys ())},
216
- 'names' : _read_names (ttf , (_NAME_ID_VERSION ,
217
- _NAME_ID_POSTSCRIPT_NAME , _NAME_ID_LICENSE_URL )),
218
- 'OS2' : _read_os2 (ttf ),
219
- 'post' : _read_post (ttf ),
220
- 'fvar' : _read_fvar (ttf ),
221
- 'cmap' : _read_cmap (ttf ),
222
- 'color' : _read_color (ttf ),
223
- 'features' : _read_features (ttf ),
224
- 'counts' : _read_codepoint_glyph_counts (ttf ),
245
+ "table_sizes" : {
246
+ tag : reader .tables [tag ].length for tag in sorted (reader .keys ())
247
+ },
248
+ "head" : _read_head (ttf ),
249
+ "names" : _read_names (
250
+ ttf , (_NAME_ID_VERSION , _NAME_ID_POSTSCRIPT_NAME , _NAME_ID_LICENSE_URL )
251
+ ),
252
+ "OS2" : _read_os2 (ttf ),
253
+ "post" : _read_post (ttf ),
254
+ "fvar" : _read_fvar (ttf ),
255
+ "cmap" : _read_cmap (ttf ),
256
+ "color" : _read_color (ttf ),
257
+ "features" : _read_features (ttf ),
258
+ "counts" : _read_codepoint_glyph_counts (ttf ),
225
259
}
226
260
ttf .close ()
227
261
228
- return {k : v for k ,v in metadata .items () if v is not None }
262
+ return {k : v for k , v in metadata .items () if v is not None }
229
263
230
264
231
265
def main ():
232
266
import pprint
233
267
import sys
268
+
234
269
for filename in sys .argv [1 :]:
235
270
pp = pprint .PrettyPrinter ()
236
271
pp .pprint (read_metadata (filename ))
237
272
273
+
238
274
if __name__ == "__main__" :
239
- main ()
275
+ main ()
0 commit comments