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.
Files changed (24) hide show
  1. {fontforge_variable_font-0.4.0/fontforge_variable_font.egg-info → fontforge_variable_font-0.4.2}/PKG-INFO +1 -1
  2. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/delete.py +5 -4
  3. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/design_axes.py +20 -10
  4. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/export.py +43 -31
  5. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/instance.py +12 -5
  6. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/language.py +1 -1
  7. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/load.py +20 -13
  8. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/utils.py +60 -2
  9. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2/fontforge_variable_font.egg-info}/PKG-INFO +1 -1
  10. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/setup.cfg +1 -1
  11. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/LICENSE +0 -0
  12. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/README.md +0 -0
  13. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/__init__.py +0 -0
  14. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforgeVF/__main__.py +0 -0
  15. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforge_variable_font.egg-info/SOURCES.txt +0 -0
  16. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforge_variable_font.egg-info/dependency_links.txt +0 -0
  17. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforge_variable_font.egg-info/entry_points.txt +0 -0
  18. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforge_variable_font.egg-info/requires.txt +0 -0
  19. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/fontforge_variable_font.egg-info/top_level.txt +0 -0
  20. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/pyproject.toml +0 -0
  21. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/test/test_design_axes.py +0 -0
  22. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/test/test_export.py +0 -0
  23. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/test/test_language.py +0 -0
  24. {fontforge_variable_font-0.4.0 → fontforge_variable_font-0.4.2}/test/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fontforge_variable_font
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: A FontForge_plugin to create a variable font
5
5
  Home-page: https://github.com/MihailJP/fontforge-variable-font
6
6
  Author: MihailJP
@@ -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, glyph):
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(fontforge.activeFont())
30
+ deleteVFInfo(font)
30
31
 
31
32
 
32
- def deleteVFInfoEnable(u, glyph):
33
- return utils.vfInfoExists(fontforge.activeFont())
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.getVFValue(font, addr):
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.getVFValue(font, 'axes.' + k + '.localNames', {}).keys())
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.getVFValue(font, 'axes.' + k + '.map', [])
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(utils.intOrFloat(el) & 1))
288
+ None if el == '' else bool(int(el) & 1))
280
289
  utils.setOrDeleteVFValue(font, valAddr + '.elidable',
281
- None if el == '' else bool(utils.intOrFloat(el) & 2))
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 = utils.getVFValue(font, 'axes.' + k + '.tag', '????') \
198
+ tag = str(
199
+ utils.getVFValue(font, 'axes.' + k + '.tag', '????')
198
200
  if k.startswith('custom') else k
199
- name = utils.getVFValue(font, 'axes.' + k + '.name', v['name']) \
201
+ )
202
+ name = str(
203
+ utils.getVFValue(font, 'axes.' + k + '.name', v['name'])
200
204
  if k.startswith('custom') else v['name']
201
- s.location[name] = getAxisValue(f, tag)
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 = utils.getVFValue(font, 'axes.' + k + '.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
- for instance in utils.getVFValue(font, 'instances', []):
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.getVFValue(font, 'axes.' + k + '.localNames', {})
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.getVFValue(
345
+ localNames = utils.getVFValueAsDict(
338
346
  font, 'axes.' + tag + '.labels.' + str(int(value)) + '.localNames',
339
- utils.getVFValue(
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.getVFValue(
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, language.languageCodeReverseLookup(i[0]))
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, glyph):
584
- if result := _saveMenuDialog(fontforge.activeFont()):
585
- _exportVariableFont(fontforge.activeFont(), result)
595
+ def saveMenu(u, font: fontforge.font):
596
+ if result := _saveMenuDialog(font):
597
+ _exportVariableFont(font, result)
586
598
 
587
599
 
588
- def saveEnable(u, glyph):
589
- return utils.vfInfoExists(fontforge.activeFont())
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.getVFValue(font, 'instances', [])
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
- numberOfInstances = max(((len(utils.getVFValue(font, 'instances', [])) + 7) // 4) * 4, 8)
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(utils.getVFValue(font, 'instances', []))):
68
+ for i in range(len(instances)):
66
69
  for k, v in designAxes.items():
67
- languages |= set(utils.getVFValue(font, 'instances[' + str(i) + '].localNames', {}).keys())
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
- numberOfInstances = max(((len(utils.getVFValue(font, 'instances', [])) + 7) // 4) * 4, 8)
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.endswith('.ttf') or font.path.endswith('.woff2'):
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fontforge_variable_font
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: A FontForge_plugin to create a variable font
5
5
  Home-page: https://github.com/MihailJP/fontforge-variable-font
6
6
  Author: MihailJP
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = fontforge_variable_font
3
- version = 0.4.0
3
+ version = 0.4.2
4
4
  author = MihailJP
5
5
  author_email = mihailjp@gmail.com
6
6
  description = A FontForge_plugin to create a variable font