fontforge-variable-font 0.4.0__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fontforge_variable_font-0.4.0/fontforge_variable_font.egg-info → fontforge_variable_font-0.4.2}/PKG-INFO +1 -1
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/delete.py +5 -4
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/design_axes.py +20 -10
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/export.py +43 -31
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/instance.py +12 -5
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/language.py +1 -1
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/load.py +20 -13
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/utils.py +60 -2
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2/fontforge_variable_font.egg-info}/PKG-INFO +1 -1
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/setup.cfg +1 -1
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/LICENSE +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/README.md +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/__init__.py +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/__main__.py +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforge_variable_font.egg-info/SOURCES.txt +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforge_variable_font.egg-info/dependency_links.txt +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforge_variable_font.egg-info/entry_points.txt +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforge_variable_font.egg-info/requires.txt +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforge_variable_font.egg-info/top_level.txt +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/pyproject.toml +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/test/test_design_axes.py +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/test/test_export.py +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/test/test_language.py +0 -0
- {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/test/test_utils.py +0 -0
|
@@ -13,6 +13,7 @@ def deleteVFInfo(font: fontforge.font) -> bool:
|
|
|
13
13
|
:return: ``True`` if the key was deleted, ``False`` otherwise.
|
|
14
14
|
"""
|
|
15
15
|
if utils.vfInfoExists(font):
|
|
16
|
+
assert isinstance(font.persistent, dict)
|
|
16
17
|
del font.persistent['VF']
|
|
17
18
|
if len(font.persistent) == 0:
|
|
18
19
|
font.persistent = None
|
|
@@ -21,13 +22,13 @@ def deleteVFInfo(font: fontforge.font) -> bool:
|
|
|
21
22
|
return False
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
def deleteVFInfoMenu(u,
|
|
25
|
+
def deleteVFInfoMenu(u, font: fontforge.font):
|
|
25
26
|
"""Menu emtry to delete VF info from UI
|
|
26
27
|
|
|
27
28
|
This menu is enabled if active font has VF info.
|
|
28
29
|
"""
|
|
29
|
-
deleteVFInfo(
|
|
30
|
+
deleteVFInfo(font)
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
def deleteVFInfoEnable(u,
|
|
33
|
-
return utils.vfInfoExists(
|
|
33
|
+
def deleteVFInfoEnable(u, font: fontforge.font):
|
|
34
|
+
return utils.vfInfoExists(font)
|
|
@@ -24,7 +24,7 @@ def _searchCustomAxis(font: fontforge.font, tag: str) -> str | None:
|
|
|
24
24
|
return tag
|
|
25
25
|
else:
|
|
26
26
|
for k, v in designAxes.items():
|
|
27
|
-
if k.startswith('custom') and utils.getVFValue(font, 'axes.' + k + '.tag', '').rstrip() == tag.rstrip():
|
|
27
|
+
if k.startswith('custom') and str(utils.getVFValue(font, 'axes.' + k + '.tag', '')).rstrip() == tag.rstrip():
|
|
28
28
|
return k
|
|
29
29
|
return None
|
|
30
30
|
|
|
@@ -56,21 +56,21 @@ def _getFlags(font: fontforge.font, labelAddr: str) -> int:
|
|
|
56
56
|
|
|
57
57
|
def _loadLabels(tag: str, lang=None):
|
|
58
58
|
font = fontforge.activeFont()
|
|
59
|
+
assert font is not None
|
|
59
60
|
addr = 'axes.' + tag + '.labels'
|
|
60
|
-
if label := utils.
|
|
61
|
+
if label := utils.getVFValueAsDict(font, addr):
|
|
61
62
|
text = ''
|
|
62
|
-
assert isinstance(label, dict)
|
|
63
63
|
for k, v in label.items():
|
|
64
64
|
labelAddr = addr + '.' + str(k).replace('.', ',') # escape decimal point
|
|
65
65
|
if lang:
|
|
66
66
|
text += str(k) + ',' + \
|
|
67
|
-
utils.getVFValue(font, labelAddr + '.localNames.' + hex(lang), '') + \
|
|
67
|
+
str(utils.getVFValue(font, labelAddr + '.localNames.' + hex(lang), '')) + \
|
|
68
68
|
', '
|
|
69
69
|
else:
|
|
70
70
|
text += str(k) + ',' + \
|
|
71
71
|
str(_getFlags(font, labelAddr)) + \
|
|
72
72
|
',' + str(utils.getVFValue(font, labelAddr + '.linkedValue', '')) + \
|
|
73
|
-
',' + utils.getVFValue(font, labelAddr + '.name', '') + \
|
|
73
|
+
',' + str(utils.getVFValue(font, labelAddr + '.name', '')) + \
|
|
74
74
|
', '
|
|
75
75
|
return text[:-2]
|
|
76
76
|
elif utils.vfInfoExists(font):
|
|
@@ -101,9 +101,10 @@ def _loadLabels(tag: str, lang=None):
|
|
|
101
101
|
def _prepareQuestions_languages(questions):
|
|
102
102
|
from fontforgeVF.language import getLanguageList
|
|
103
103
|
font = fontforge.activeFont()
|
|
104
|
+
assert font is not None
|
|
104
105
|
languages = set()
|
|
105
106
|
for k, v in designAxes.items():
|
|
106
|
-
languages |= set(utils.
|
|
107
|
+
languages |= set(utils.getVFValueAsDict(font, 'axes.' + k + '.localNames').keys())
|
|
107
108
|
languages = tuple(languages)
|
|
108
109
|
localNameCategory = len(questions)
|
|
109
110
|
localNameRange = range(1, max(((len(languages) + 7) // 4) * 4, 8)+1)
|
|
@@ -116,6 +117,7 @@ def _prepareQuestions_languages(questions):
|
|
|
116
117
|
|
|
117
118
|
def _prepareQuestions_values(questions, k, v):
|
|
118
119
|
font = fontforge.activeFont()
|
|
120
|
+
assert font is not None
|
|
119
121
|
tagStat = 1 if utils.getVFValue(font, 'axes.' + k + '.active', False) else 0
|
|
120
122
|
if tagStat == 1 and not k.startswith('custom') and utils.getVFValue(font, 'axes.' + k + '.useDefault', False):
|
|
121
123
|
tagStat = 2
|
|
@@ -155,19 +157,21 @@ def _prepareQuestions_values(questions, k, v):
|
|
|
155
157
|
|
|
156
158
|
def _prepareQuestions_map(questions, k, v):
|
|
157
159
|
font = fontforge.activeFont()
|
|
160
|
+
assert font is not None
|
|
158
161
|
questions[2]["questions"].append({
|
|
159
162
|
'type': 'string',
|
|
160
163
|
'question': v["name"] + ':',
|
|
161
164
|
'tag': k + 'map',
|
|
162
165
|
'default': ', '.join(list(map(
|
|
163
166
|
lambda x: str(x[0]) + ',' + str(x[1]),
|
|
164
|
-
utils.
|
|
167
|
+
utils.getVFValueAsList(font, 'axes.' + k + '.map')
|
|
165
168
|
))),
|
|
166
169
|
})
|
|
167
170
|
|
|
168
171
|
|
|
169
172
|
def _prepareQuestions_order(questions, k, v):
|
|
170
173
|
font = fontforge.activeFont()
|
|
174
|
+
assert font is not None
|
|
171
175
|
questions[3]["questions"].append({
|
|
172
176
|
'type': 'string',
|
|
173
177
|
'question': v["name"] + ':',
|
|
@@ -178,6 +182,7 @@ def _prepareQuestions_order(questions, k, v):
|
|
|
178
182
|
|
|
179
183
|
def _prepareQuestions_names(questions, k, v):
|
|
180
184
|
font = fontforge.activeFont()
|
|
185
|
+
assert font is not None
|
|
181
186
|
questions[4]["questions"].append({
|
|
182
187
|
'type': 'string',
|
|
183
188
|
'question': v["name"] + ':',
|
|
@@ -198,6 +203,7 @@ def _prepareQuestions_names(questions, k, v):
|
|
|
198
203
|
|
|
199
204
|
def _prepareQuestions_localNames(questions, k, v, languages, localNameRange, localNameCategory):
|
|
200
205
|
font = fontforge.activeFont()
|
|
206
|
+
assert font is not None
|
|
201
207
|
for i in localNameRange:
|
|
202
208
|
questions[localNameCategory + i - 1]["questions"].append({
|
|
203
209
|
'type': 'string',
|
|
@@ -217,6 +223,7 @@ def _prepareQuestions_localNames(questions, k, v, languages, localNameRange, loc
|
|
|
217
223
|
|
|
218
224
|
def _prepareQuestions_custom(questions, k, v):
|
|
219
225
|
font = fontforge.activeFont()
|
|
226
|
+
assert font is not None
|
|
220
227
|
if k.startswith('custom'):
|
|
221
228
|
questions[1]["questions"].append({
|
|
222
229
|
'type': 'string',
|
|
@@ -249,6 +256,7 @@ def _prepareQuestions():
|
|
|
249
256
|
# Save result
|
|
250
257
|
def _saveResult_values(result, k, v):
|
|
251
258
|
font = fontforge.activeFont()
|
|
259
|
+
assert font is not None
|
|
252
260
|
utils.setVFValue(font, 'axes.' + k + '.active', result[k] != 'unset')
|
|
253
261
|
if not k.startswith('custom') and result[k] != 'unset':
|
|
254
262
|
utils.setVFValue(font, 'axes.' + k + '.useDefault', result[k] == 'auto')
|
|
@@ -266,6 +274,7 @@ def _saveResult_values(result, k, v):
|
|
|
266
274
|
|
|
267
275
|
def _saveResult_labels(result, k, v):
|
|
268
276
|
font = fontforge.activeFont()
|
|
277
|
+
assert font is not None
|
|
269
278
|
utils.setOrDeleteVFValue(font, 'axes.' + k + '.order', int(result[k + 'order']) if result[k + 'order'] else None)
|
|
270
279
|
utils.deleteVFValue(font, 'axes.' + k + '.labels')
|
|
271
280
|
if result[k + 'labels']:
|
|
@@ -276,9 +285,9 @@ def _saveResult_labels(result, k, v):
|
|
|
276
285
|
val = val.replace('.', ',') # escape decimal point
|
|
277
286
|
valAddr = 'axes.' + k + '.labels.' + val
|
|
278
287
|
utils.setOrDeleteVFValue(font, valAddr + '.olderSibling',
|
|
279
|
-
None if el == '' else bool(
|
|
288
|
+
None if el == '' else bool(int(el) & 1))
|
|
280
289
|
utils.setOrDeleteVFValue(font, valAddr + '.elidable',
|
|
281
|
-
None if el == '' else bool(
|
|
290
|
+
None if el == '' else bool(int(el) & 2))
|
|
282
291
|
utils.setOrDeleteVFValue(font, valAddr + '.linkedValue',
|
|
283
292
|
None if lv == '' else utils.intOrFloat(lv))
|
|
284
293
|
utils.setOrDeleteVFValue(font, valAddr + '.name',
|
|
@@ -295,6 +304,7 @@ def _saveResult_labels(result, k, v):
|
|
|
295
304
|
|
|
296
305
|
def _saveResult_localNames(result, k, v):
|
|
297
306
|
font = fontforge.activeFont()
|
|
307
|
+
assert font is not None
|
|
298
308
|
utils.setOrDeleteVFValue(font, 'axes.' + k + '.name', result[k + 'name'])
|
|
299
309
|
utils.deleteVFValue(font, 'axes.' + k + '.localNames')
|
|
300
310
|
for i in (x.replace('lang', '') for x in result if x.startswith('lang')):
|
|
@@ -417,6 +427,6 @@ def designAxesMenu(u, glyph):
|
|
|
417
427
|
* Axis value
|
|
418
428
|
* Name
|
|
419
429
|
"""
|
|
420
|
-
result = fontforge.askMulti("Design axes", _prepareQuestions())
|
|
430
|
+
result = fontforge.askMulti("Design axes", _prepareQuestions()) # type: ignore
|
|
421
431
|
if result:
|
|
422
432
|
_saveResult(result)
|
|
@@ -30,7 +30,7 @@ def _axisMinValue(defaultFont: fontforge.font, tag: str, filterItalicRoman: bool
|
|
|
30
30
|
getAxisValue(f, tag) for f
|
|
31
31
|
in _getSourceFonts(defaultFont, filterItalicRoman)
|
|
32
32
|
if getAxisValue(f, tag) is not None
|
|
33
|
-
)
|
|
33
|
+
) # type: ignore
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
|
|
@@ -40,7 +40,7 @@ def _axisMaxValue(defaultFont: fontforge.font, tag: str, filterItalicRoman: bool
|
|
|
40
40
|
getAxisValue(f, tag) for f
|
|
41
41
|
in _getSourceFonts(defaultFont, filterItalicRoman)
|
|
42
42
|
if getAxisValue(f, tag) is not None
|
|
43
|
-
)
|
|
43
|
+
) # type: ignore
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
|
|
@@ -130,7 +130,7 @@ def _outputUfo(font: fontforge.font, outputDir: str | PathLike, outputFile: str
|
|
|
130
130
|
import re
|
|
131
131
|
# import shutil # for debug
|
|
132
132
|
|
|
133
|
-
assert outputFile.endswith('.ufo')
|
|
133
|
+
assert str(outputFile).endswith('.ufo')
|
|
134
134
|
ufoPath = str(outputDir) + '/' + str(outputFile)
|
|
135
135
|
changed = font.changed
|
|
136
136
|
unlinkRmOvrlpSave = {}
|
|
@@ -148,10 +148,10 @@ def _outputUfo(font: fontforge.font, outputDir: str | PathLike, outputFile: str
|
|
|
148
148
|
info = _ufoInfo()
|
|
149
149
|
ufo.readInfo(info)
|
|
150
150
|
if _isFixedPitch(font):
|
|
151
|
-
ufo.postscriptIsFixedPitch = True
|
|
152
|
-
ufo.styleMapFamilyName = _getFontFamilyName(font)
|
|
153
|
-
ufo.styleMapStyleName = _getFontSubFamilyName(font)
|
|
154
|
-
ufo.writeInfo(info)
|
|
151
|
+
ufo.postscriptIsFixedPitch = True # type: ignore
|
|
152
|
+
ufo.styleMapFamilyName = _getFontFamilyName(font) # type: ignore
|
|
153
|
+
ufo.styleMapStyleName = _getFontSubFamilyName(font) # type: ignore
|
|
154
|
+
ufo.writeInfo(info) # type: ignore
|
|
155
155
|
|
|
156
156
|
if _aaltExists(font) or _allGSUBTags(font):
|
|
157
157
|
feat = ufo.readFeatures()
|
|
@@ -169,7 +169,7 @@ def _outputUfo(font: fontforge.font, outputDir: str | PathLike, outputFile: str
|
|
|
169
169
|
if aaltInclude or existingAalt:
|
|
170
170
|
newAalt = "feature aalt {\n" + aaltInclude + existingAalt + "} aalt;\n\n"
|
|
171
171
|
feat = re.sub(featPosPattern, newAalt, feat, count=1)
|
|
172
|
-
ufo.writeFeatures(feat)
|
|
172
|
+
ufo.writeFeatures(feat) # type: ignore
|
|
173
173
|
|
|
174
174
|
font.changed = changed
|
|
175
175
|
|
|
@@ -182,6 +182,7 @@ def _designSpaceSources(font: fontforge.font, doc: DesignSpaceDocument, filterIt
|
|
|
182
182
|
# print(_getSourceFonts(font))
|
|
183
183
|
for f in _getSourceFonts(font, filterItalicRoman):
|
|
184
184
|
s = SourceDescriptor()
|
|
185
|
+
assert isinstance(f.temporary, dict)
|
|
185
186
|
s.path = f.temporary['ufo']
|
|
186
187
|
if f is font:
|
|
187
188
|
s.copyLib = True
|
|
@@ -194,11 +195,15 @@ def _designSpaceSources(font: fontforge.font, doc: DesignSpaceDocument, filterIt
|
|
|
194
195
|
for k, v in designAxes.items():
|
|
195
196
|
active = 'axes.' + k + '.active'
|
|
196
197
|
if utils.getVFValue(font, active, False) and utils.getVFValue(f, active, False):
|
|
197
|
-
tag =
|
|
198
|
+
tag = str(
|
|
199
|
+
utils.getVFValue(font, 'axes.' + k + '.tag', '????')
|
|
198
200
|
if k.startswith('custom') else k
|
|
199
|
-
|
|
201
|
+
)
|
|
202
|
+
name = str(
|
|
203
|
+
utils.getVFValue(font, 'axes.' + k + '.name', v['name'])
|
|
200
204
|
if k.startswith('custom') else v['name']
|
|
201
|
-
|
|
205
|
+
)
|
|
206
|
+
s.location[name] = getAxisValue(f, tag) # type: ignore
|
|
202
207
|
s.familyName = _getFontFamilyName(f)
|
|
203
208
|
s.styleName = _getFontSubFamilyName(f)
|
|
204
209
|
doc.addSource(s)
|
|
@@ -207,7 +212,7 @@ def _designSpaceSources(font: fontforge.font, doc: DesignSpaceDocument, filterIt
|
|
|
207
212
|
def _designSpaceAxes_labels(labels, a: AxisDescriptor | DiscreteAxisDescriptor):
|
|
208
213
|
L = []
|
|
209
214
|
for u, d in labels.items():
|
|
210
|
-
if not (a.minimum <= u <= a.maximum):
|
|
215
|
+
if not (a.minimum <= u <= a.maximum): # type: ignore
|
|
211
216
|
fontforge.logWarning('Ignored label {0} = {1} because out of range'.format(
|
|
212
217
|
a.name, str(u)))
|
|
213
218
|
elif 'name' not in d:
|
|
@@ -229,15 +234,17 @@ def _designSpaceAxes(font: fontforge.font, doc: DesignSpaceDocument, filterItali
|
|
|
229
234
|
for k, v in designAxes.items():
|
|
230
235
|
if utils.getVFValue(font, 'axes.' + k + '.active', False):
|
|
231
236
|
a = DiscreteAxisDescriptor() if k == 'ital' else AxisDescriptor()
|
|
232
|
-
a.tag =
|
|
237
|
+
a.tag = str(
|
|
238
|
+
utils.getVFValue(font, 'axes.' + k + '.tag', '????')
|
|
233
239
|
if k.startswith('custom') else k
|
|
240
|
+
)
|
|
234
241
|
if k == 'ital' and filterItalicRoman is not None:
|
|
235
|
-
a.minimum = 1 if filterItalicRoman else _axisMinValue(font, a.tag)
|
|
236
|
-
a.maximum = 0 if not filterItalicRoman else _axisMaxValue(font, a.tag)
|
|
242
|
+
a.minimum = 1 if filterItalicRoman else _axisMinValue(font, a.tag) # type: ignore
|
|
243
|
+
a.maximum = 0 if not filterItalicRoman else _axisMaxValue(font, a.tag) # type: ignore
|
|
237
244
|
else:
|
|
238
|
-
a.minimum = _axisMinValue(font, a.tag)
|
|
239
|
-
a.maximum = _axisMaxValue(font, a.tag)
|
|
240
|
-
a.default = getAxisValue(font, a.tag)
|
|
245
|
+
a.minimum = _axisMinValue(font, a.tag) # type: ignore
|
|
246
|
+
a.maximum = _axisMaxValue(font, a.tag) # type: ignore
|
|
247
|
+
a.default = getAxisValue(font, a.tag) # type: ignore
|
|
241
248
|
a.name = utils.getVFValue(font, 'axes.' + k + '.name', v['name'])
|
|
242
249
|
if val := utils.getVFValue(font, 'axes.' + k + '.map'):
|
|
243
250
|
a.map = val
|
|
@@ -249,7 +256,8 @@ def _designSpaceAxes(font: fontforge.font, doc: DesignSpaceDocument, filterItali
|
|
|
249
256
|
|
|
250
257
|
|
|
251
258
|
def _designSpaceInstances(font: fontforge.font, doc: DesignSpaceDocument, filterItalicRoman: bool | None = None):
|
|
252
|
-
|
|
259
|
+
instances = utils.getVFValueAsList(font, 'instances')
|
|
260
|
+
for instance in instances:
|
|
253
261
|
i = InstanceDescriptor()
|
|
254
262
|
i.styleName = instance['name']
|
|
255
263
|
i.postScriptFontName = instance['psName']
|
|
@@ -322,7 +330,7 @@ def _fixTtf_axes(font: fontforge.font, ttf: ttLib.TTFont):
|
|
|
322
330
|
if k.startswith('custom') else k
|
|
323
331
|
)
|
|
324
332
|
if utils.getVFValue(font, 'axes.' + k + '.active', False):
|
|
325
|
-
localNames = utils.
|
|
333
|
+
localNames = utils.getVFValueAsDict(font, 'axes.' + k + '.localNames')
|
|
326
334
|
for lang, name in localNames.items():
|
|
327
335
|
axis = [a for a in ttf["fvar"].axes if a.axisTag == tag]
|
|
328
336
|
if axis:
|
|
@@ -334,9 +342,9 @@ def _fixTtf_labels(font: fontforge.font, ttf: ttLib.TTFont):
|
|
|
334
342
|
axisIndex = axisLabel.AxisIndex
|
|
335
343
|
tag = ttf['STAT'].table.DesignAxisRecord.Axis[axisIndex].AxisTag
|
|
336
344
|
value = axisLabel.Value
|
|
337
|
-
localNames = utils.
|
|
345
|
+
localNames = utils.getVFValueAsDict(
|
|
338
346
|
font, 'axes.' + tag + '.labels.' + str(int(value)) + '.localNames',
|
|
339
|
-
utils.
|
|
347
|
+
utils.getVFValueAsDict(
|
|
340
348
|
font, 'axes.' + tag + '.labels.' + str(float(value)).replace('.', ',') + '.localNames',
|
|
341
349
|
{}
|
|
342
350
|
)
|
|
@@ -348,7 +356,7 @@ def _fixTtf_labels(font: fontforge.font, ttf: ttLib.TTFont):
|
|
|
348
356
|
def _fixTtf_instances(font: fontforge.font, ttf: ttLib.TTFont):
|
|
349
357
|
for i, instance in enumerate(ttf['fvar'].instances):
|
|
350
358
|
subfamilyNameID = instance.subfamilyNameID
|
|
351
|
-
localNames = utils.
|
|
359
|
+
localNames = utils.getVFValueAsDict(
|
|
352
360
|
font, 'instances[' + str(i) + '].localNames', {}
|
|
353
361
|
)
|
|
354
362
|
for lang, name in localNames.items():
|
|
@@ -359,8 +367,12 @@ def _fixTtf(font: fontforge.font, filename: str | PathLike):
|
|
|
359
367
|
with ttLib.TTFont(str(filename)) as ttf:
|
|
360
368
|
for i in font.sfnt_names:
|
|
361
369
|
if i[0] != 'English (US)':
|
|
370
|
+
if isinstance(i[0], str): # likely
|
|
371
|
+
langCode = language.languageCodeReverseLookup(i[0])
|
|
372
|
+
else: # unlikely
|
|
373
|
+
langCode = i[0]
|
|
362
374
|
if i[1] in _fields:
|
|
363
|
-
ttf['name'].setName(i[2], _fields[i[1]], 3, 1,
|
|
375
|
+
ttf['name'].setName(i[2], _fields[i[1]], 3, 1, langCode)
|
|
364
376
|
_fixTtf_axes(font, ttf)
|
|
365
377
|
_fixTtf_labels(font, ttf)
|
|
366
378
|
_fixTtf_instances(font, ttf)
|
|
@@ -540,7 +552,7 @@ def _exportVariableFont(font: fontforge.font, dialogResult: dict[str, str]):
|
|
|
540
552
|
|
|
541
553
|
def _saveMenuDialog(font: fontforge.font) -> dict | None:
|
|
542
554
|
if _hasBothRomanAndItalic(font):
|
|
543
|
-
questions = [
|
|
555
|
+
questions: list = [
|
|
544
556
|
{
|
|
545
557
|
'type': 'savepath', 'question': '_Roman VF:', 'tag': 'file',
|
|
546
558
|
'default':
|
|
@@ -557,7 +569,7 @@ def _saveMenuDialog(font: fontforge.font) -> dict | None:
|
|
|
557
569
|
},
|
|
558
570
|
]
|
|
559
571
|
else:
|
|
560
|
-
questions = [
|
|
572
|
+
questions: list = [
|
|
561
573
|
{
|
|
562
574
|
'type': 'savepath', 'question': '_Save as:', 'tag': 'file',
|
|
563
575
|
'default':
|
|
@@ -580,10 +592,10 @@ def _saveMenuDialog(font: fontforge.font) -> dict | None:
|
|
|
580
592
|
return fontforge.askMulti("Save variable font", questions)
|
|
581
593
|
|
|
582
594
|
|
|
583
|
-
def saveMenu(u,
|
|
584
|
-
if result := _saveMenuDialog(
|
|
585
|
-
_exportVariableFont(
|
|
595
|
+
def saveMenu(u, font: fontforge.font):
|
|
596
|
+
if result := _saveMenuDialog(font):
|
|
597
|
+
_exportVariableFont(font, result)
|
|
586
598
|
|
|
587
599
|
|
|
588
|
-
def saveEnable(u,
|
|
589
|
-
return utils.vfInfoExists(
|
|
600
|
+
def saveEnable(u, font: fontforge.font):
|
|
601
|
+
return utils.vfInfoExists(font)
|
|
@@ -5,7 +5,7 @@ from .design_axes import designAxes
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def _instances_getval(font: fontforge.font, cnt: int, key: str, defaultVal):
|
|
8
|
-
instances = utils.
|
|
8
|
+
instances = utils.getVFValueAsList(font, 'instances')
|
|
9
9
|
if (cnt - 1) < len(instances):
|
|
10
10
|
if isinstance(instances[cnt - 1], dict):
|
|
11
11
|
if key in instances[cnt - 1]:
|
|
@@ -15,6 +15,7 @@ def _instances_getval(font: fontforge.font, cnt: int, key: str, defaultVal):
|
|
|
15
15
|
|
|
16
16
|
def _prepareQuestions_instances(cnt: int):
|
|
17
17
|
font = fontforge.activeFont()
|
|
18
|
+
assert font is not None
|
|
18
19
|
questions = [
|
|
19
20
|
{
|
|
20
21
|
'type': 'string',
|
|
@@ -58,13 +59,16 @@ def _prepareQuestions_instanceLocalNames(questions: list):
|
|
|
58
59
|
from math import ceil
|
|
59
60
|
|
|
60
61
|
font = fontforge.activeFont()
|
|
61
|
-
|
|
62
|
+
assert font is not None
|
|
63
|
+
instances = utils.getVFValueAsList(font, 'instances')
|
|
64
|
+
numberOfInstances = max(((len(instances) + 7) // 4) * 4, 8)
|
|
62
65
|
|
|
63
66
|
languages = set()
|
|
64
67
|
numberOfLanguages = 8
|
|
65
|
-
for i in range(len(
|
|
68
|
+
for i in range(len(instances)):
|
|
66
69
|
for k, v in designAxes.items():
|
|
67
|
-
|
|
70
|
+
langdict = utils.getVFValueAsDict(font, 'instances[' + str(i) + '].localNames')
|
|
71
|
+
languages |= set(langdict.keys())
|
|
68
72
|
languages = tuple(languages)
|
|
69
73
|
numberOfLanguages = max(((len(languages) + 7) // 4) * 4, 8)
|
|
70
74
|
|
|
@@ -101,8 +105,10 @@ def _prepareQuestions_instanceLocalNames(questions: list):
|
|
|
101
105
|
|
|
102
106
|
def _prepareQuestions():
|
|
103
107
|
font = fontforge.activeFont()
|
|
108
|
+
assert font is not None
|
|
104
109
|
questions = []
|
|
105
|
-
|
|
110
|
+
instances = utils.getVFValueAsList(font, 'instances')
|
|
111
|
+
numberOfInstances = max(((len(instances) + 7) // 4) * 4, 8)
|
|
106
112
|
|
|
107
113
|
for i in range(numberOfInstances):
|
|
108
114
|
questions.append({
|
|
@@ -116,6 +122,7 @@ def _prepareQuestions():
|
|
|
116
122
|
|
|
117
123
|
def _saveInstances(result: dict):
|
|
118
124
|
font = fontforge.activeFont()
|
|
125
|
+
assert font is not None
|
|
119
126
|
instanceList = []
|
|
120
127
|
cnt = 1
|
|
121
128
|
while 'psName' + str(cnt) in result:
|
|
@@ -825,7 +825,7 @@ def getLanguageList(listNumber: int, defaultCode: int | None = None):
|
|
|
825
825
|
for langId, langCode, langName in sorted(languageCodeIterator(), key=lambda x: x[2]):
|
|
826
826
|
languageList.append({'name': langName, 'tag': hex(langId), 'default': langId == defaultCode})
|
|
827
827
|
questions = {
|
|
828
|
-
'category': 'Localized names ' + (
|
|
828
|
+
'category': 'Localized names ' + str(
|
|
829
829
|
languageCodeLookup(defaultCode) if defaultCode else str(listNumber)
|
|
830
830
|
),
|
|
831
831
|
'questions': [
|
|
@@ -4,7 +4,7 @@ from os import PathLike
|
|
|
4
4
|
import fontforge
|
|
5
5
|
from fontTools import ttLib
|
|
6
6
|
|
|
7
|
-
from .utils import intOrFloat, checkExtensionTtfOrWoff2
|
|
7
|
+
from .utils import intOrFloat, checkExtensionTtfOrWoff2, ensureTuple
|
|
8
8
|
from fontforge_plugin_helper import addFontGenerateHook
|
|
9
9
|
|
|
10
10
|
|
|
@@ -61,7 +61,7 @@ def _denormalize(minimum, default, maximum, value):
|
|
|
61
61
|
def _getVFData_fvar_axes(ttf: ttLib.TTFont, axisValues: dict[str, int | float], vfData: dict):
|
|
62
62
|
for axis in ttf['fvar'].axes:
|
|
63
63
|
tag = axis.axisTag
|
|
64
|
-
axisData = {'active': True}
|
|
64
|
+
axisData: dict[str, str | int | float] = {'active': True}
|
|
65
65
|
if tag in axisValues:
|
|
66
66
|
axisData['useDefault'] = False
|
|
67
67
|
axisData['value'] = intOrFloat(axisValues[tag])
|
|
@@ -208,6 +208,7 @@ def _doOpenVariableFont(
|
|
|
208
208
|
partial.save(instancePath)
|
|
209
209
|
font = fontforge.open(instancePath)
|
|
210
210
|
initPersistentDict(font)
|
|
211
|
+
assert isinstance(font.persistent, dict)
|
|
211
212
|
font.persistent['VF'] = vfData
|
|
212
213
|
return font
|
|
213
214
|
|
|
@@ -219,12 +220,12 @@ def _openVF(
|
|
|
219
220
|
) -> fontforge.font:
|
|
220
221
|
with ttLib.TTFont(filename) as ttf:
|
|
221
222
|
if 'fvar' not in ttf:
|
|
222
|
-
return fontforge.open(filename)
|
|
223
|
+
return fontforge.open(str(filename))
|
|
223
224
|
elif isinstance(axisValuesOrInstance, dict):
|
|
224
225
|
if [axis for axis in ttf['fvar'].axes if axis.minValue != axis.maxValue]:
|
|
225
226
|
return _doOpenVariableFont(filename, axisValuesOrInstance, ttf, tmpdir)
|
|
226
227
|
else:
|
|
227
|
-
return fontforge.open(filename)
|
|
228
|
+
return fontforge.open(str(filename))
|
|
228
229
|
elif isinstance(axisValuesOrInstance, int):
|
|
229
230
|
return _doOpenVariableFont(
|
|
230
231
|
filename,
|
|
@@ -287,10 +288,11 @@ def openVariableFont(
|
|
|
287
288
|
filetype = checkExtensionTtfOrWoff2(filename)
|
|
288
289
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
289
290
|
if filetype == 'ttf':
|
|
290
|
-
_openVF(filename, axisValuesOrInstance, tmpdir)
|
|
291
|
+
font = _openVF(filename, axisValuesOrInstance, tmpdir)
|
|
291
292
|
else: # woff2
|
|
292
293
|
ttFile = _woff2Decompress(filename, tmpdir)
|
|
293
|
-
_openVF(ttFile, axisValuesOrInstance, tmpdir)
|
|
294
|
+
font = _openVF(ttFile, axisValuesOrInstance, tmpdir)
|
|
295
|
+
return font
|
|
294
296
|
|
|
295
297
|
|
|
296
298
|
def _setParameterDialog(filename: str | PathLike, ttf: ttLib.TTFont, tmpdir: str | PathLike):
|
|
@@ -311,8 +313,8 @@ def _setParameterDialog(filename: str | PathLike, ttf: ttLib.TTFont, tmpdir: str
|
|
|
311
313
|
|
|
312
314
|
if result := fontforge.askMulti('Please specify an instance to open', questions):
|
|
313
315
|
for key in result:
|
|
314
|
-
result[key] = intOrFloat(result[key])
|
|
315
|
-
_doOpenVariableFont(filename, result, ttf, tmpdir)
|
|
316
|
+
result[key] = intOrFloat(result[key]) # type: ignore
|
|
317
|
+
_doOpenVariableFont(filename, result, ttf, tmpdir) # type: ignore
|
|
316
318
|
|
|
317
319
|
|
|
318
320
|
def _chooseInstanceDialog(filename: str | PathLike, ttf: ttLib.TTFont, tmpdir: str | PathLike):
|
|
@@ -320,10 +322,10 @@ def _chooseInstanceDialog(filename: str | PathLike, ttf: ttLib.TTFont, tmpdir: s
|
|
|
320
322
|
result = fontforge.askChoices(
|
|
321
323
|
'Choose instance(s) to open',
|
|
322
324
|
'Instances in this font',
|
|
323
|
-
[str(ttf['name'].getName(i.subfamilyNameID, 3, 1, 0x409)) for i in ttf['fvar'].instances],
|
|
325
|
+
tuple([str(ttf['name'].getName(i.subfamilyNameID, 3, 1, 0x409)) for i in ttf['fvar'].instances]),
|
|
324
326
|
multiple=True
|
|
325
327
|
)
|
|
326
|
-
for i in (x[1] for x in zip(result, ttf['fvar'].instances) if x[0]):
|
|
328
|
+
for i in (x[1] for x in zip(ensureTuple(result), ttf['fvar'].instances) if x[0]):
|
|
327
329
|
_doOpenVariableFont(filename, i.coordinates, ttf, tmpdir)
|
|
328
330
|
|
|
329
331
|
|
|
@@ -367,6 +369,7 @@ def loadMenu(u, glyph):
|
|
|
367
369
|
def _generatePreHook(font: fontforge.font, target: str):
|
|
368
370
|
from fontforgeVF.utils import vfInfoExists
|
|
369
371
|
|
|
372
|
+
assert isinstance(font.temporary, dict)
|
|
370
373
|
changed = font.changed
|
|
371
374
|
if vfInfoExists(font):
|
|
372
375
|
if target.endswith('.ttf') or target.endswith('.woff2'):
|
|
@@ -375,7 +378,7 @@ def _generatePreHook(font: fontforge.font, target: str):
|
|
|
375
378
|
"This font has variable font metadata in its persistent dict.\n"
|
|
376
379
|
"Did you intend to output a variable font?\n"
|
|
377
380
|
"(all masters need to be opened beforehand)",
|
|
378
|
-
["_Yes", "_No"],
|
|
381
|
+
["_Yes", "_No"], # type: ignore
|
|
379
382
|
)
|
|
380
383
|
font.temporary['generateVF'] = (ans == 0)
|
|
381
384
|
font.changed = changed
|
|
@@ -385,6 +388,7 @@ def _generatePostHook(font: fontforge.font, target: str):
|
|
|
385
388
|
from fontforgeVF.utils import vfInfoExists
|
|
386
389
|
from fontforgeVF.export import exportVariableFont
|
|
387
390
|
|
|
391
|
+
assert isinstance(font.temporary, dict)
|
|
388
392
|
changed = font.changed
|
|
389
393
|
if vfInfoExists(font):
|
|
390
394
|
if 'generateVF' in font.temporary:
|
|
@@ -421,19 +425,22 @@ def _loadHook_ttf(font: fontforge.font):
|
|
|
421
425
|
"_Yes",
|
|
422
426
|
"Open with _parameters",
|
|
423
427
|
"_No",
|
|
424
|
-
]
|
|
428
|
+
] # type: ignore
|
|
425
429
|
)
|
|
426
430
|
if ans == 0 or ans == 1:
|
|
427
431
|
_selectInstanceDialog(font.path, ttf, ans)
|
|
428
432
|
else:
|
|
429
433
|
_selectInstanceDialog(font.path, ttf, 1)
|
|
430
434
|
initPersistentDict(font)
|
|
435
|
+
assert isinstance(font.persistent, dict)
|
|
431
436
|
vfData = _getVFData(ttf, {})
|
|
432
437
|
font.persistent['VF'] = vfData
|
|
433
438
|
|
|
434
439
|
|
|
435
440
|
def loadHook(font: fontforge.font):
|
|
436
|
-
if font.path
|
|
441
|
+
if not font.path: # may occur in CID fonts
|
|
442
|
+
_addGenerateHook(font)
|
|
443
|
+
elif font.path.endswith('.ttf') or font.path.endswith('.woff2'):
|
|
437
444
|
_loadHook_ttf(font)
|
|
438
445
|
_addGenerateHook(font)
|
|
439
446
|
elif font.path.endswith('.sfd'):
|
|
@@ -4,7 +4,7 @@ import re
|
|
|
4
4
|
import fontforge
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def intOrFloat(val):
|
|
7
|
+
def intOrFloat(val: str | int | float):
|
|
8
8
|
"""Convert to ``int`` or ``float`` if possible
|
|
9
9
|
|
|
10
10
|
If parameter represents an integer, returns it converted to ``int``
|
|
@@ -31,6 +31,33 @@ def intOrFloat(val):
|
|
|
31
31
|
return f
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def ensureList(obj) -> list:
|
|
35
|
+
if obj is None:
|
|
36
|
+
return []
|
|
37
|
+
elif isinstance(obj, list):
|
|
38
|
+
return obj
|
|
39
|
+
else:
|
|
40
|
+
return list(obj)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def ensureTuple(obj) -> tuple:
|
|
44
|
+
if obj is None:
|
|
45
|
+
return ()
|
|
46
|
+
elif isinstance(obj, tuple):
|
|
47
|
+
return obj
|
|
48
|
+
else:
|
|
49
|
+
return tuple(obj)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def ensureDict(obj) -> dict:
|
|
53
|
+
if obj is None:
|
|
54
|
+
return {}
|
|
55
|
+
elif isinstance(obj, dict):
|
|
56
|
+
return obj
|
|
57
|
+
else:
|
|
58
|
+
return dict(obj)
|
|
59
|
+
|
|
60
|
+
|
|
34
61
|
def initPersistentDict(font: fontforge.font):
|
|
35
62
|
"""Make sure ``font.persistent`` is a ``dict``
|
|
36
63
|
|
|
@@ -123,6 +150,7 @@ def getVFValue(font: fontforge.font, key: str, default=None):
|
|
|
123
150
|
if not vfInfoExists(font):
|
|
124
151
|
return default
|
|
125
152
|
else:
|
|
153
|
+
assert isinstance(font.persistent, dict)
|
|
126
154
|
info = font.persistent["VF"]
|
|
127
155
|
for part in _checkVFAddr(key):
|
|
128
156
|
c, k = part
|
|
@@ -137,6 +165,34 @@ def getVFValue(font: fontforge.font, key: str, default=None):
|
|
|
137
165
|
return info
|
|
138
166
|
|
|
139
167
|
|
|
168
|
+
def getVFValueAsList(font: fontforge.font, key: str, default: list = []) -> list:
|
|
169
|
+
"""Gets a value from VF info
|
|
170
|
+
|
|
171
|
+
Same as ``getVFValue()`` but ensures returning a list
|
|
172
|
+
|
|
173
|
+
:param font: Fontforge font object
|
|
174
|
+
:param key: Name of key
|
|
175
|
+
:param default: Optional. Returns this value if ``key`` does not \
|
|
176
|
+
exist. Without this parameter defaults to ``[]``.
|
|
177
|
+
:return: the value for ``key``, or ``default`` if no such ``key``.
|
|
178
|
+
"""
|
|
179
|
+
return ensureList(getVFValue(font, key, default))
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def getVFValueAsDict(font: fontforge.font, key: str, default: dict = {}) -> dict:
|
|
183
|
+
"""Gets a value from VF info
|
|
184
|
+
|
|
185
|
+
Same as ``getVFValue()`` but ensures returning a dict
|
|
186
|
+
|
|
187
|
+
:param font: Fontforge font object
|
|
188
|
+
:param key: Name of key
|
|
189
|
+
:param default: Optional. Returns this value if ``key`` does not \
|
|
190
|
+
exist. Without this parameter defaults to ``{}``.
|
|
191
|
+
:return: the value for ``key``, or ``default`` if no such ``key``.
|
|
192
|
+
"""
|
|
193
|
+
return ensureDict(getVFValue(font, key, default))
|
|
194
|
+
|
|
195
|
+
|
|
140
196
|
def _makeSureKeyExists(container, key):
|
|
141
197
|
if isinstance(container, list):
|
|
142
198
|
while len(container) <= key:
|
|
@@ -167,6 +223,7 @@ def setVFValue(font: fontforge.font, key: str, val):
|
|
|
167
223
|
:raises ``RuntimeError``: user refused ``initPersistentDict``.
|
|
168
224
|
"""
|
|
169
225
|
initPersistentDict(font)
|
|
226
|
+
assert isinstance(font.persistent, dict)
|
|
170
227
|
parent = font.persistent
|
|
171
228
|
info = parent["VF"]
|
|
172
229
|
prevk = "VF"
|
|
@@ -188,7 +245,7 @@ def setVFValue(font: fontforge.font, key: str, val):
|
|
|
188
245
|
parent[k] = val
|
|
189
246
|
|
|
190
247
|
|
|
191
|
-
def _deleteEmptyLists(d: dict):
|
|
248
|
+
def _deleteEmptyLists(d: dict | list):
|
|
192
249
|
if isinstance(d, list):
|
|
193
250
|
for i in range(len(d)):
|
|
194
251
|
if isinstance(d[i], dict):
|
|
@@ -241,6 +298,7 @@ def deleteVFValue(font: fontforge.font, key: str) -> bool:
|
|
|
241
298
|
:return: ``True`` if the key was deleted, ``False`` otherwise.
|
|
242
299
|
"""
|
|
243
300
|
if vfInfoExists(font):
|
|
301
|
+
assert isinstance(font.persistent, dict)
|
|
244
302
|
parent = font.persistent
|
|
245
303
|
info = parent["VF"]
|
|
246
304
|
for part in _checkVFAddr(key):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|