fontforge-variable-font 0.2.0__tar.gz → 0.3.0__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.2.0 → fontforge_variable_font-0.3.0}/PKG-INFO +24 -1
- fontforge_variable_font-0.2.0/fontforge_variable_font.egg-info/PKG-INFO → fontforge_variable_font-0.3.0/README.md +19 -17
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforgeVF/__main__.py +26 -4
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforgeVF/design_axes.py +2 -6
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforgeVF/export.py +16 -22
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforgeVF/instance.py +1 -5
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforgeVF/load.py +108 -16
- fontforge_variable_font-0.2.0/README.md → fontforge_variable_font-0.3.0/fontforge_variable_font.egg-info/PKG-INFO +40 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/setup.cfg +7 -1
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/LICENSE +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforgeVF/__init__.py +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforgeVF/delete.py +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforgeVF/language.py +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforgeVF/utils.py +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforge_variable_font.egg-info/SOURCES.txt +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforge_variable_font.egg-info/dependency_links.txt +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforge_variable_font.egg-info/entry_points.txt +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforge_variable_font.egg-info/requires.txt +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/fontforge_variable_font.egg-info/top_level.txt +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/pyproject.toml +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/test/test_design_axes.py +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/test/test_export.py +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/test/test_language.py +0 -0
- {fontforge_variable_font-0.2.0 → fontforge_variable_font-0.3.0}/test/test_utils.py +0 -0
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fontforge_variable_font
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
7
7
|
Author-email: mihailjp@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Plugins
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
8
12
|
Classifier: Programming Language :: Python :: 3
|
|
9
13
|
Classifier: Operating System :: OS Independent
|
|
10
14
|
Classifier: Topic :: Text Processing :: Fonts
|
|
@@ -330,6 +334,25 @@ Deletes VF data.
|
|
|
330
334
|
> [!WARNING]
|
|
331
335
|
> You will see **no** warning.
|
|
332
336
|
|
|
337
|
+
### Hooks
|
|
338
|
+
|
|
339
|
+
This plugin installs new/open font hooks which does:
|
|
340
|
+
|
|
341
|
+
- sets font generation hooks to output VF if metadata exists
|
|
342
|
+
- If you export a TTF or a WOFF2 when VF metadata exists, you will be
|
|
343
|
+
asked if you intend a VF. In this case too, all the masters must be
|
|
344
|
+
opened beforehands, however unlike the dedicated menu, VF-specific
|
|
345
|
+
options or italic counterpart cannot be set.
|
|
346
|
+
- For technical reason, first the static font gets exported as usual,
|
|
347
|
+
then VF overwrites it. Failed attempt of exporting a VF leaves the
|
|
348
|
+
static font.
|
|
349
|
+
- loads VF-specific metadata if available
|
|
350
|
+
- If you load a variable font from the ordinary 'load' menu, you will be
|
|
351
|
+
asked if you will open additional instances and which one(s.)
|
|
352
|
+
|
|
353
|
+
> [!NOTE]
|
|
354
|
+
> These hooks work in interactive mode only.
|
|
355
|
+
|
|
333
356
|
### Script usage
|
|
334
357
|
|
|
335
358
|
As a Python module, in addition to `fontforge` module, scripting to export
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: fontforge_variable_font
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: A FontForge_plugin to create a variable font
|
|
5
|
-
Home-page: https://github.com/MihailJP/fontforge-variable-font
|
|
6
|
-
Author: MihailJP
|
|
7
|
-
Author-email: mihailjp@gmail.com
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Classifier: Topic :: Text Processing :: Fonts
|
|
11
|
-
Requires-Python: >=3.10
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
Requires-Dist: fonttools
|
|
15
|
-
Requires-Dist: fontmake
|
|
16
|
-
Dynamic: license-file
|
|
17
|
-
|
|
18
1
|
Fontforge Variable Font Plugin
|
|
19
2
|
==============================
|
|
20
3
|
|
|
@@ -330,6 +313,25 @@ Deletes VF data.
|
|
|
330
313
|
> [!WARNING]
|
|
331
314
|
> You will see **no** warning.
|
|
332
315
|
|
|
316
|
+
### Hooks
|
|
317
|
+
|
|
318
|
+
This plugin installs new/open font hooks which does:
|
|
319
|
+
|
|
320
|
+
- sets font generation hooks to output VF if metadata exists
|
|
321
|
+
- If you export a TTF or a WOFF2 when VF metadata exists, you will be
|
|
322
|
+
asked if you intend a VF. In this case too, all the masters must be
|
|
323
|
+
opened beforehands, however unlike the dedicated menu, VF-specific
|
|
324
|
+
options or italic counterpart cannot be set.
|
|
325
|
+
- For technical reason, first the static font gets exported as usual,
|
|
326
|
+
then VF overwrites it. Failed attempt of exporting a VF leaves the
|
|
327
|
+
static font.
|
|
328
|
+
- loads VF-specific metadata if available
|
|
329
|
+
- If you load a variable font from the ordinary 'load' menu, you will be
|
|
330
|
+
asked if you will open additional instances and which one(s.)
|
|
331
|
+
|
|
332
|
+
> [!NOTE]
|
|
333
|
+
> These hooks work in interactive mode only.
|
|
334
|
+
|
|
333
335
|
### Script usage
|
|
334
336
|
|
|
335
337
|
As a Python module, in addition to `fontforge` module, scripting to export
|
|
@@ -6,12 +6,30 @@ from fontforgeVF import (
|
|
|
6
6
|
load
|
|
7
7
|
)
|
|
8
8
|
import fontforge
|
|
9
|
+
from typing import Literal, Callable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _addHook(
|
|
13
|
+
name: Literal['newFontHook', 'loadFontHook'],
|
|
14
|
+
hook: Callable[[fontforge.font], None]
|
|
15
|
+
):
|
|
16
|
+
assert isinstance(fontforge.hooks, dict)
|
|
17
|
+
if name in fontforge.hooks:
|
|
18
|
+
currentHook = fontforge.hooks[name]
|
|
19
|
+
|
|
20
|
+
def chainHook(font: fontforge.font):
|
|
21
|
+
currentHook(font)
|
|
22
|
+
hook(font)
|
|
23
|
+
|
|
24
|
+
fontforge.hooks[name] = chainHook
|
|
25
|
+
else:
|
|
26
|
+
fontforge.hooks[name] = hook
|
|
9
27
|
|
|
10
28
|
|
|
11
29
|
def fontforge_plugin_init(**kw):
|
|
12
30
|
fontforge.registerMenuItem(
|
|
13
31
|
callback=load.loadMenu,
|
|
14
|
-
enable=
|
|
32
|
+
enable=None,
|
|
15
33
|
context="Font",
|
|
16
34
|
submenu=["_Variable Font", '_Open a variable font'],
|
|
17
35
|
name="By named _instance...",
|
|
@@ -19,7 +37,7 @@ def fontforge_plugin_init(**kw):
|
|
|
19
37
|
)
|
|
20
38
|
fontforge.registerMenuItem(
|
|
21
39
|
callback=load.loadMenu,
|
|
22
|
-
enable=
|
|
40
|
+
enable=None,
|
|
23
41
|
context="Font",
|
|
24
42
|
submenu=["_Variable Font", '_Open a variable font'],
|
|
25
43
|
name="By _parameter...",
|
|
@@ -34,14 +52,14 @@ def fontforge_plugin_init(**kw):
|
|
|
34
52
|
)
|
|
35
53
|
fontforge.registerMenuItem(
|
|
36
54
|
callback=design_axes.designAxesMenu,
|
|
37
|
-
enable=
|
|
55
|
+
enable=None,
|
|
38
56
|
context="Font",
|
|
39
57
|
submenu="_Variable Font",
|
|
40
58
|
name="Design _axes..."
|
|
41
59
|
)
|
|
42
60
|
fontforge.registerMenuItem(
|
|
43
61
|
callback=instance.instanceMenu,
|
|
44
|
-
enable=
|
|
62
|
+
enable=None,
|
|
45
63
|
context="Font",
|
|
46
64
|
submenu="_Variable Font",
|
|
47
65
|
name="Named _instances..."
|
|
@@ -53,3 +71,7 @@ def fontforge_plugin_init(**kw):
|
|
|
53
71
|
submenu="_Variable Font",
|
|
54
72
|
name="_Delete VF info"
|
|
55
73
|
)
|
|
74
|
+
|
|
75
|
+
if fontforge.hasUserInterface:
|
|
76
|
+
_addHook('loadFontHook', load.loadHook)
|
|
77
|
+
_addHook('newFontHook', load.newFontHook)
|
|
@@ -288,7 +288,7 @@ def _saveResult_labels(result, k, v):
|
|
|
288
288
|
utils.setOrDeleteVFValue(font, valAddr + '.name',
|
|
289
289
|
None if name == '' else name)
|
|
290
290
|
utils.deleteVFValue(font, 'axes.' + k + '.labels.' + val + '.localNames')
|
|
291
|
-
for i in
|
|
291
|
+
for i in (x.replace('lang', '') for x in result if x.startswith('lang')):
|
|
292
292
|
if result['lang' + i] and result[k + 'labels' + i]:
|
|
293
293
|
for val, name in list(zip(_x := iter(result[k + 'labels' + i].split(',')), _x)):
|
|
294
294
|
val = val.replace('.', ',') # escape decimal point
|
|
@@ -301,7 +301,7 @@ def _saveResult_localNames(result, k, v):
|
|
|
301
301
|
font = fontforge.activeFont()
|
|
302
302
|
utils.setOrDeleteVFValue(font, 'axes.' + k + '.name', result[k + 'name'])
|
|
303
303
|
utils.deleteVFValue(font, 'axes.' + k + '.localNames')
|
|
304
|
-
for i in
|
|
304
|
+
for i in (x.replace('lang', '') for x in result if x.startswith('lang')):
|
|
305
305
|
if result['lang' + i]:
|
|
306
306
|
utils.setOrDeleteVFValue(
|
|
307
307
|
font, 'axes.' + k + '.localNames.' + result['lang' + i],
|
|
@@ -424,7 +424,3 @@ def designAxesMenu(u, glyph):
|
|
|
424
424
|
result = fontforge.askMulti("Design axes", _prepareQuestions())
|
|
425
425
|
if result:
|
|
426
426
|
_saveResult(result)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
def designAxesEnable(u, glyph):
|
|
430
|
-
return True
|
|
@@ -20,33 +20,29 @@ __all__ = [
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def _getSourceFonts(defaultFont: fontforge.font, filterItalicRoman: bool | None = None) -> list[fontforge.font]:
|
|
23
|
-
tmpIter =
|
|
23
|
+
tmpIter = (font for font in fontforge.fonts() if font.familyname == defaultFont.familyname)
|
|
24
24
|
if filterItalicRoman is None:
|
|
25
25
|
return list(tmpIter)
|
|
26
26
|
else:
|
|
27
|
-
return
|
|
27
|
+
return [font for font in tmpIter if getAxisValue(font, 'ital') == filterItalicRoman]
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def _axisMinValue(defaultFont: fontforge.font, tag: str, filterItalicRoman: bool | None = None) -> int | float:
|
|
31
31
|
return min(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
_getSourceFonts(defaultFont, filterItalicRoman)
|
|
37
|
-
)
|
|
32
|
+
(
|
|
33
|
+
getAxisValue(f, tag) for f
|
|
34
|
+
in _getSourceFonts(defaultFont, filterItalicRoman)
|
|
35
|
+
if getAxisValue(f, tag) is not None
|
|
38
36
|
)
|
|
39
37
|
)
|
|
40
38
|
|
|
41
39
|
|
|
42
40
|
def _axisMaxValue(defaultFont: fontforge.font, tag: str, filterItalicRoman: bool | None = None) -> int | float:
|
|
43
41
|
return max(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
_getSourceFonts(defaultFont, filterItalicRoman)
|
|
49
|
-
)
|
|
42
|
+
(
|
|
43
|
+
getAxisValue(f, tag) for f
|
|
44
|
+
in _getSourceFonts(defaultFont, filterItalicRoman)
|
|
45
|
+
if getAxisValue(f, tag) is not None
|
|
50
46
|
)
|
|
51
47
|
)
|
|
52
48
|
|
|
@@ -86,12 +82,7 @@ def _getFontSubFamilyName(font: fontforge.font, lang: str = 'English (US)') -> s
|
|
|
86
82
|
def _isFixedPitch(font: fontforge.font) -> bool:
|
|
87
83
|
return len(
|
|
88
84
|
set(
|
|
89
|
-
|
|
90
|
-
lambda x: x.width,
|
|
91
|
-
filter(
|
|
92
|
-
lambda x: 0x20 <= x.unicode <= 0x7e, font.glyphs()
|
|
93
|
-
)
|
|
94
|
-
)
|
|
85
|
+
(glyph.width for glyph in font.glyphs() if 0x20 <= glyph.unicode <= 0x7e)
|
|
95
86
|
)
|
|
96
87
|
) == 1
|
|
97
88
|
|
|
@@ -336,7 +327,7 @@ def _fixTtf_axes(font: fontforge.font, ttf: ttLib.TTFont):
|
|
|
336
327
|
if utils.getVFValue(font, 'axes.' + k + '.active', False):
|
|
337
328
|
localNames = utils.getVFValue(font, 'axes.' + k + '.localNames', {})
|
|
338
329
|
for lang, name in localNames.items():
|
|
339
|
-
axis =
|
|
330
|
+
axis = [a for a in ttf["fvar"].axes if a.axisTag == tag]
|
|
340
331
|
if axis:
|
|
341
332
|
ttf['name'].setName(name, axis[0].axisNameID, 3, 1, lang)
|
|
342
333
|
|
|
@@ -426,10 +417,13 @@ def _exportVF(
|
|
|
426
417
|
_doExportVF(font, tmpdir, italicFilename, tmpdir + '/vf2.designspace', options)
|
|
427
418
|
except CalledProcessError as e:
|
|
428
419
|
if fontforge.hasUserInterface():
|
|
420
|
+
cmd = e.cmd
|
|
421
|
+
if isinstance(cmd, str):
|
|
422
|
+
cmd = e.cmd.split(' ')
|
|
429
423
|
fontforge.logWarning(e.stderr)
|
|
430
424
|
fontforge.postError(
|
|
431
425
|
"Failed to export",
|
|
432
|
-
"'{0}' failed with return code {1}".format(
|
|
426
|
+
"'{0}' failed with return code {1}".format(cmd[0], e.returncode)
|
|
433
427
|
)
|
|
434
428
|
else:
|
|
435
429
|
raise
|
|
@@ -129,7 +129,7 @@ def _saveInstances(result: dict):
|
|
|
129
129
|
else:
|
|
130
130
|
instance[k] = utils.intOrFloat(result[k + '_' + str(cnt)])
|
|
131
131
|
instance['localNames'] = {}
|
|
132
|
-
for lang in
|
|
132
|
+
for lang in (x for x in result.keys() if x.startswith('lang') and x.find('name') == -1):
|
|
133
133
|
if result[lang] and result[lang + 'name' + str(cnt)]:
|
|
134
134
|
instance['localNames'][utils.intOrFloat(result[lang])] = result[lang + 'name' + str(cnt)]
|
|
135
135
|
instanceList.append(instance)
|
|
@@ -169,7 +169,3 @@ def instanceMenu(u, glyph):
|
|
|
169
169
|
result = fontforge.askMulti("Named instances", _prepareQuestions())
|
|
170
170
|
if result:
|
|
171
171
|
_saveInstances(result)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def instanceEnable(u, glyph):
|
|
175
|
-
return True
|
|
@@ -3,6 +3,7 @@ from fontforgeVF.utils import intOrFloat, checkExtensionTtfOrWoff2
|
|
|
3
3
|
from os import PathLike
|
|
4
4
|
from fontTools import ttLib
|
|
5
5
|
import faulthandler
|
|
6
|
+
from typing import Literal, Callable
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
@@ -42,10 +43,7 @@ def _loadInstanceNames(varfont: ttLib.TTFont, partial: ttLib.TTFont, postscriptN
|
|
|
42
43
|
def _addNames(ttf: ttLib.TTFont, data: dict, nameID: int):
|
|
43
44
|
data['name'] = ''
|
|
44
45
|
data['localNames'] = {}
|
|
45
|
-
for name in
|
|
46
|
-
lambda x: x.nameID == nameID and x.platformID == 3 and x.platEncID == 1,
|
|
47
|
-
ttf['name'].names
|
|
48
|
-
):
|
|
46
|
+
for name in (n for n in ttf['name'].names if n.nameID == nameID and n.platformID == 3 and n.platEncID == 1):
|
|
49
47
|
if name.langID == 0x409:
|
|
50
48
|
data['name'] = str(name)
|
|
51
49
|
else:
|
|
@@ -133,7 +131,7 @@ def _getVFData_STAT(ttf: ttLib.TTFont, vfData: dict):
|
|
|
133
131
|
def _getVFData_customTags(ttf: ttLib.TTFont, vfData: dict):
|
|
134
132
|
from fontforgeVF.design_axes import designAxes
|
|
135
133
|
|
|
136
|
-
customAxes =
|
|
134
|
+
customAxes = [axis for axis in vfData['axes'].keys() if axis not in designAxes.keys()]
|
|
137
135
|
for i, a in enumerate(customAxes):
|
|
138
136
|
if i < 3:
|
|
139
137
|
vfData['axes']['custom' + str(i + 1)] = vfData['axes'][a]
|
|
@@ -186,7 +184,7 @@ def _doOpenVariableFont(
|
|
|
186
184
|
from fontforgeVF.utils import initPersistentDict
|
|
187
185
|
from pathlib import Path
|
|
188
186
|
|
|
189
|
-
axes = [a.axisTag for a in
|
|
187
|
+
axes = [a.axisTag for a in varfont["fvar"].axes if a.minValue < a.maxValue]
|
|
190
188
|
if extra := set(axisValues.keys()) - set(axes): # extra axes set
|
|
191
189
|
fontforge.logWarning(', '.join(["'" + a + "'" for a in list(extra)]) + ' ignored')
|
|
192
190
|
for tag in list(extra):
|
|
@@ -194,7 +192,7 @@ def _doOpenVariableFont(
|
|
|
194
192
|
if unset := set(axes) - set(axisValues.keys()): # unset axes
|
|
195
193
|
fontforge.logWarning(', '.join(["'" + a + "'" for a in list(unset)]) + ' not set (default value used)')
|
|
196
194
|
for tag in list(unset):
|
|
197
|
-
axisValues[tag] = [a.defaultValue for a in
|
|
195
|
+
axisValues[tag] = [a.defaultValue for a in varfont["fvar"].axes if a.axisTag == tag][0]
|
|
198
196
|
_checkAxisValue(varfont, axisValues) # out of range
|
|
199
197
|
vfData = _getVFData(varfont, axisValues)
|
|
200
198
|
|
|
@@ -226,7 +224,7 @@ def _openVF(
|
|
|
226
224
|
if 'fvar' not in ttf:
|
|
227
225
|
return fontforge.open(filename)
|
|
228
226
|
elif isinstance(axisValuesOrInstance, dict):
|
|
229
|
-
if
|
|
227
|
+
if [axis for axis in ttf['fvar'].axes if axis.minValue != axis.maxValue]:
|
|
230
228
|
return _doOpenVariableFont(filename, axisValuesOrInstance, ttf, tmpdir)
|
|
231
229
|
else:
|
|
232
230
|
return fontforge.open(filename)
|
|
@@ -240,10 +238,10 @@ def _openVF(
|
|
|
240
238
|
elif isinstance(axisValuesOrInstance, str):
|
|
241
239
|
return _doOpenVariableFont(
|
|
242
240
|
filename,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
ttf['
|
|
246
|
-
|
|
241
|
+
[
|
|
242
|
+
i for i in ttf['fvar'].instances
|
|
243
|
+
if str(ttf['name'].getName(i.subfamilyNameID, 3, 1, 0x409)) == axisValuesOrInstance
|
|
244
|
+
][0].coordinates,
|
|
247
245
|
ttf,
|
|
248
246
|
tmpdir
|
|
249
247
|
)
|
|
@@ -328,7 +326,7 @@ def _chooseInstanceDialog(filename: str | PathLike, ttf: ttLib.TTFont, tmpdir: s
|
|
|
328
326
|
[str(ttf['name'].getName(i.subfamilyNameID, 3, 1, 0x409)) for i in ttf['fvar'].instances],
|
|
329
327
|
multiple=True
|
|
330
328
|
)
|
|
331
|
-
for i in [
|
|
329
|
+
for i in (x[1] for x in zip(result, ttf['fvar'].instances) if x[0]):
|
|
332
330
|
_doOpenVariableFont(filename, i.coordinates, ttf, tmpdir)
|
|
333
331
|
|
|
334
332
|
|
|
@@ -361,7 +359,7 @@ def loadMenu(u, glyph):
|
|
|
361
359
|
if 'fvar' not in ttf:
|
|
362
360
|
fontforge.logWarning(filename + " does not have 'fvar' table")
|
|
363
361
|
fontforge.open(filename)
|
|
364
|
-
elif
|
|
362
|
+
elif [axis for axis in ttf['fvar'].axes if axis.minValue != axis.maxValue]:
|
|
365
363
|
_selectInstanceDialog(filename, ttf, u)
|
|
366
364
|
else:
|
|
367
365
|
fontforge.logWarning(filename + " has 'fvar' table but all axes are fixed")
|
|
@@ -369,5 +367,99 @@ def loadMenu(u, glyph):
|
|
|
369
367
|
faulthandler.disable()
|
|
370
368
|
|
|
371
369
|
|
|
372
|
-
def
|
|
373
|
-
|
|
370
|
+
def _generatePreHook(font: fontforge.font, target: str):
|
|
371
|
+
from fontforgeVF.utils import vfInfoExists
|
|
372
|
+
|
|
373
|
+
changed = font.changed
|
|
374
|
+
if vfInfoExists(font):
|
|
375
|
+
if target.endswith('.ttf') or target.endswith('.woff2'):
|
|
376
|
+
ans = fontforge.ask(
|
|
377
|
+
"Variable font",
|
|
378
|
+
"This font has variable font metadata in its persistent dict.\n"
|
|
379
|
+
"Did you intend to output a variable font?\n"
|
|
380
|
+
"(all masters need to be opened beforehand)",
|
|
381
|
+
["_Yes", "_No"],
|
|
382
|
+
)
|
|
383
|
+
font.temporary['generateVF'] = (ans == 0)
|
|
384
|
+
font.changed = changed
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _generatePostHook(font: fontforge.font, target: str):
|
|
388
|
+
from fontforgeVF.utils import vfInfoExists
|
|
389
|
+
from fontforgeVF.export import exportVariableFont
|
|
390
|
+
|
|
391
|
+
changed = font.changed
|
|
392
|
+
if vfInfoExists(font):
|
|
393
|
+
if 'generateVF' in font.temporary:
|
|
394
|
+
if target.endswith('.ttf') or target.endswith('.woff2'):
|
|
395
|
+
if font.temporary['generateVF']:
|
|
396
|
+
exportVariableFont(font, target, None)
|
|
397
|
+
try:
|
|
398
|
+
del font.temporary['generateVF']
|
|
399
|
+
except KeyError:
|
|
400
|
+
pass
|
|
401
|
+
font.changed = changed
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _addHook(
|
|
405
|
+
font: fontforge.font,
|
|
406
|
+
name: Literal['generateFontPreHook', 'generateFontPostHook'],
|
|
407
|
+
hook: Callable[[fontforge.font, str], None]
|
|
408
|
+
):
|
|
409
|
+
assert isinstance(font.temporary, dict)
|
|
410
|
+
if name in font.temporary:
|
|
411
|
+
currentHook = font.temporary[name]
|
|
412
|
+
|
|
413
|
+
def chainHook(font: fontforge.font, target: str):
|
|
414
|
+
currentHook(font, target)
|
|
415
|
+
hook(font, target)
|
|
416
|
+
|
|
417
|
+
font.temporary[name] = chainHook
|
|
418
|
+
else:
|
|
419
|
+
font.temporary[name] = hook
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _addGenerateHook(font: fontforge.font):
|
|
423
|
+
if not isinstance(font.temporary, dict):
|
|
424
|
+
font.temporary = {}
|
|
425
|
+
_addHook(font, 'generateFontPreHook', _generatePreHook)
|
|
426
|
+
_addHook(font, 'generateFontPostHook', _generatePostHook)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _loadHook_ttf(font: fontforge.font):
|
|
430
|
+
from fontforgeVF.utils import initPersistentDict
|
|
431
|
+
|
|
432
|
+
with ttLib.TTFont(font.path) as ttf:
|
|
433
|
+
if 'fvar' in ttf and [axis for axis in ttf['fvar'].axes if axis.minValue != axis.maxValue]:
|
|
434
|
+
if ttf['fvar'].instances:
|
|
435
|
+
ans = fontforge.ask(
|
|
436
|
+
"Variable font",
|
|
437
|
+
"The font '" + font.familyname + "' in \n"
|
|
438
|
+
"'" + font.path + "'\n"
|
|
439
|
+
"seems to be a variable font.\n"
|
|
440
|
+
"Would you like to open another instance of this font?",
|
|
441
|
+
[
|
|
442
|
+
"_Yes",
|
|
443
|
+
"Open with _parameters",
|
|
444
|
+
"_No",
|
|
445
|
+
]
|
|
446
|
+
)
|
|
447
|
+
if ans == 0 or ans == 1:
|
|
448
|
+
_selectInstanceDialog(font.path, ttf, ans)
|
|
449
|
+
else:
|
|
450
|
+
_selectInstanceDialog(font.path, ttf, 1)
|
|
451
|
+
initPersistentDict(font)
|
|
452
|
+
vfData = _getVFData(ttf, {})
|
|
453
|
+
font.persistent['VF'] = vfData
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def loadHook(font: fontforge.font):
|
|
457
|
+
if font.path.endswith('.ttf') or font.path.endswith('.woff2'):
|
|
458
|
+
_loadHook_ttf(font)
|
|
459
|
+
_addGenerateHook(font)
|
|
460
|
+
elif font.path.endswith('.sfd'):
|
|
461
|
+
_addGenerateHook(font)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def newFontHook(font: fontforge.font):
|
|
465
|
+
_addGenerateHook(font)
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fontforge_variable_font
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A FontForge_plugin to create a variable font
|
|
5
|
+
Home-page: https://github.com/MihailJP/fontforge-variable-font
|
|
6
|
+
Author: MihailJP
|
|
7
|
+
Author-email: mihailjp@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Plugins
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Text Processing :: Fonts
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: fonttools
|
|
19
|
+
Requires-Dist: fontmake
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
1
22
|
Fontforge Variable Font Plugin
|
|
2
23
|
==============================
|
|
3
24
|
|
|
@@ -313,6 +334,25 @@ Deletes VF data.
|
|
|
313
334
|
> [!WARNING]
|
|
314
335
|
> You will see **no** warning.
|
|
315
336
|
|
|
337
|
+
### Hooks
|
|
338
|
+
|
|
339
|
+
This plugin installs new/open font hooks which does:
|
|
340
|
+
|
|
341
|
+
- sets font generation hooks to output VF if metadata exists
|
|
342
|
+
- If you export a TTF or a WOFF2 when VF metadata exists, you will be
|
|
343
|
+
asked if you intend a VF. In this case too, all the masters must be
|
|
344
|
+
opened beforehands, however unlike the dedicated menu, VF-specific
|
|
345
|
+
options or italic counterpart cannot be set.
|
|
346
|
+
- For technical reason, first the static font gets exported as usual,
|
|
347
|
+
then VF overwrites it. Failed attempt of exporting a VF leaves the
|
|
348
|
+
static font.
|
|
349
|
+
- loads VF-specific metadata if available
|
|
350
|
+
- If you load a variable font from the ordinary 'load' menu, you will be
|
|
351
|
+
asked if you will open additional instances and which one(s.)
|
|
352
|
+
|
|
353
|
+
> [!NOTE]
|
|
354
|
+
> These hooks work in interactive mode only.
|
|
355
|
+
|
|
316
356
|
### Script usage
|
|
317
357
|
|
|
318
358
|
As a Python module, in addition to `fontforge` module, scripting to export
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = fontforge_variable_font
|
|
3
|
-
version = 0.
|
|
3
|
+
version = 0.3.0
|
|
4
4
|
author = MihailJP
|
|
5
5
|
author_email = mihailjp@gmail.com
|
|
6
6
|
description = A FontForge_plugin to create a variable font
|
|
7
|
+
license = MIT
|
|
8
|
+
license_files =
|
|
9
|
+
LICENSE
|
|
7
10
|
long_description = file: README.md
|
|
8
11
|
long_description_content_type = text/markdown
|
|
9
12
|
url = https://github.com/MihailJP/fontforge-variable-font
|
|
10
13
|
classifiers =
|
|
14
|
+
Development Status :: 4 - Beta
|
|
15
|
+
Environment :: Plugins
|
|
16
|
+
Intended Audience :: Developers
|
|
11
17
|
Programming Language :: Python :: 3
|
|
12
18
|
Operating System :: OS Independent
|
|
13
19
|
Topic :: Text Processing :: Fonts
|
|
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
|
|
File without changes
|