fonttools 4.55.4__cp313-cp313-musllinux_1_2_aarch64.whl → 4.61.1__cp313-cp313-musllinux_1_2_aarch64.whl
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.
- fontTools/__init__.py +1 -1
- fontTools/annotations.py +30 -0
- fontTools/cffLib/CFF2ToCFF.py +65 -10
- fontTools/cffLib/__init__.py +61 -26
- fontTools/cffLib/specializer.py +4 -1
- fontTools/cffLib/transforms.py +11 -6
- fontTools/config/__init__.py +15 -0
- fontTools/cu2qu/cu2qu.c +6567 -5579
- fontTools/cu2qu/cu2qu.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/cu2qu/cu2qu.py +36 -4
- fontTools/cu2qu/ufo.py +14 -0
- fontTools/designspaceLib/__init__.py +8 -3
- fontTools/designspaceLib/statNames.py +14 -7
- fontTools/feaLib/ast.py +24 -15
- fontTools/feaLib/builder.py +139 -66
- fontTools/feaLib/error.py +1 -1
- fontTools/feaLib/lexer.c +7038 -7995
- fontTools/feaLib/lexer.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/feaLib/parser.py +75 -40
- fontTools/feaLib/variableScalar.py +6 -1
- fontTools/fontBuilder.py +50 -44
- fontTools/merge/__init__.py +1 -1
- fontTools/merge/cmap.py +33 -1
- fontTools/merge/tables.py +12 -1
- fontTools/misc/bezierTools.c +14913 -17013
- fontTools/misc/bezierTools.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/misc/bezierTools.py +4 -1
- fontTools/misc/configTools.py +3 -1
- fontTools/misc/enumTools.py +23 -0
- fontTools/misc/etree.py +4 -27
- fontTools/misc/filesystem/__init__.py +68 -0
- fontTools/misc/filesystem/_base.py +134 -0
- fontTools/misc/filesystem/_copy.py +45 -0
- fontTools/misc/filesystem/_errors.py +54 -0
- fontTools/misc/filesystem/_info.py +75 -0
- fontTools/misc/filesystem/_osfs.py +164 -0
- fontTools/misc/filesystem/_path.py +67 -0
- fontTools/misc/filesystem/_subfs.py +92 -0
- fontTools/misc/filesystem/_tempfs.py +34 -0
- fontTools/misc/filesystem/_tools.py +34 -0
- fontTools/misc/filesystem/_walk.py +55 -0
- fontTools/misc/filesystem/_zipfs.py +204 -0
- fontTools/misc/fixedTools.py +1 -1
- fontTools/misc/loggingTools.py +1 -1
- fontTools/misc/psCharStrings.py +17 -2
- fontTools/misc/sstruct.py +2 -6
- fontTools/misc/symfont.py +6 -8
- fontTools/misc/testTools.py +5 -1
- fontTools/misc/textTools.py +4 -2
- fontTools/misc/visitor.py +32 -16
- fontTools/misc/xmlWriter.py +44 -8
- fontTools/mtiLib/__init__.py +1 -3
- fontTools/otlLib/builder.py +402 -155
- fontTools/otlLib/optimize/gpos.py +49 -63
- fontTools/pens/filterPen.py +218 -26
- fontTools/pens/momentsPen.c +5514 -5584
- fontTools/pens/momentsPen.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/pens/pointPen.py +61 -18
- fontTools/pens/roundingPen.py +2 -2
- fontTools/pens/t2CharStringPen.py +31 -11
- fontTools/qu2cu/qu2cu.c +6581 -6168
- fontTools/qu2cu/qu2cu.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/subset/__init__.py +283 -25
- fontTools/subset/svg.py +2 -3
- fontTools/ttLib/__init__.py +4 -0
- fontTools/ttLib/__main__.py +47 -8
- fontTools/ttLib/removeOverlaps.py +7 -5
- fontTools/ttLib/reorderGlyphs.py +8 -7
- fontTools/ttLib/sfnt.py +11 -9
- fontTools/ttLib/tables/D__e_b_g.py +20 -2
- fontTools/ttLib/tables/G_V_A_R_.py +5 -0
- fontTools/ttLib/tables/S__i_l_f.py +2 -2
- fontTools/ttLib/tables/T_S_I__0.py +14 -3
- fontTools/ttLib/tables/T_S_I__1.py +2 -5
- fontTools/ttLib/tables/T_S_I__5.py +18 -7
- fontTools/ttLib/tables/__init__.py +1 -0
- fontTools/ttLib/tables/_a_v_a_r.py +12 -3
- fontTools/ttLib/tables/_c_m_a_p.py +20 -7
- fontTools/ttLib/tables/_c_v_t.py +3 -2
- fontTools/ttLib/tables/_f_p_g_m.py +3 -1
- fontTools/ttLib/tables/_g_l_y_f.py +45 -21
- fontTools/ttLib/tables/_g_v_a_r.py +67 -19
- fontTools/ttLib/tables/_h_d_m_x.py +4 -4
- fontTools/ttLib/tables/_h_m_t_x.py +7 -3
- fontTools/ttLib/tables/_l_o_c_a.py +2 -2
- fontTools/ttLib/tables/_n_a_m_e.py +11 -6
- fontTools/ttLib/tables/_p_o_s_t.py +9 -7
- fontTools/ttLib/tables/otBase.py +5 -12
- fontTools/ttLib/tables/otConverters.py +5 -2
- fontTools/ttLib/tables/otData.py +1 -1
- fontTools/ttLib/tables/otTables.py +33 -30
- fontTools/ttLib/tables/otTraverse.py +2 -1
- fontTools/ttLib/tables/sbixStrike.py +3 -3
- fontTools/ttLib/ttFont.py +666 -120
- fontTools/ttLib/ttGlyphSet.py +0 -10
- fontTools/ttLib/woff2.py +10 -13
- fontTools/ttx.py +13 -1
- fontTools/ufoLib/__init__.py +300 -202
- fontTools/ufoLib/converters.py +103 -30
- fontTools/ufoLib/errors.py +8 -0
- fontTools/ufoLib/etree.py +1 -1
- fontTools/ufoLib/filenames.py +171 -106
- fontTools/ufoLib/glifLib.py +303 -205
- fontTools/ufoLib/kerning.py +98 -48
- fontTools/ufoLib/utils.py +46 -15
- fontTools/ufoLib/validators.py +121 -99
- fontTools/unicodedata/Blocks.py +35 -20
- fontTools/unicodedata/Mirrored.py +446 -0
- fontTools/unicodedata/ScriptExtensions.py +63 -37
- fontTools/unicodedata/Scripts.py +173 -152
- fontTools/unicodedata/__init__.py +10 -2
- fontTools/varLib/__init__.py +198 -109
- fontTools/varLib/avar/__init__.py +0 -0
- fontTools/varLib/avar/__main__.py +72 -0
- fontTools/varLib/avar/build.py +79 -0
- fontTools/varLib/avar/map.py +108 -0
- fontTools/varLib/avar/plan.py +1004 -0
- fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
- fontTools/varLib/avarPlanner.py +3 -999
- fontTools/varLib/featureVars.py +21 -7
- fontTools/varLib/hvar.py +113 -0
- fontTools/varLib/instancer/__init__.py +180 -65
- fontTools/varLib/interpolatableHelpers.py +3 -0
- fontTools/varLib/iup.c +7564 -6903
- fontTools/varLib/iup.cpython-313-aarch64-linux-musl.so +0 -0
- fontTools/varLib/models.py +17 -2
- fontTools/varLib/mutator.py +11 -0
- fontTools/varLib/varStore.py +10 -38
- fontTools/voltLib/__main__.py +206 -0
- fontTools/voltLib/ast.py +4 -0
- fontTools/voltLib/parser.py +16 -8
- fontTools/voltLib/voltToFea.py +347 -166
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/METADATA +269 -1410
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/RECORD +318 -294
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/WHEEL +1 -1
- fonttools-4.61.1.dist-info/licenses/LICENSE.external +388 -0
- {fonttools-4.55.4.data → fonttools-4.61.1.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/entry_points.txt +0 -0
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info/licenses}/LICENSE +0 -0
- {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/top_level.txt +0 -0
fontTools/varLib/featureVars.py
CHANGED
|
@@ -95,6 +95,14 @@ def addFeatureVariations(font, conditionalSubstitutions, featureTag="rvrn"):
|
|
|
95
95
|
|
|
96
96
|
addFeatureVariationsRaw(font, font["GSUB"].table, conditionsAndLookups, featureTags)
|
|
97
97
|
|
|
98
|
+
# Update OS/2.usMaxContext in case the font didn't have features before, but
|
|
99
|
+
# does now, if the OS/2 table exists. The table may be required, but
|
|
100
|
+
# fontTools needs to be able to deal with non-standard fonts. Since feature
|
|
101
|
+
# variations are always 1:1 mappings, we can set the value to at least 1
|
|
102
|
+
# instead of recomputing it with `otlLib.maxContextCalc.maxCtxFont()`.
|
|
103
|
+
if (os2 := font.get("OS/2")) is not None:
|
|
104
|
+
os2.usMaxContext = max(1, os2.usMaxContext)
|
|
105
|
+
|
|
98
106
|
|
|
99
107
|
def _existingVariableFeatures(table):
|
|
100
108
|
existingFeatureVarsTags = set()
|
|
@@ -392,10 +400,13 @@ def addFeatureVariationsRaw(font, table, conditionalSubstitutions, featureTag="r
|
|
|
392
400
|
|
|
393
401
|
for scriptRecord in table.ScriptList.ScriptRecord:
|
|
394
402
|
if scriptRecord.Script.DefaultLangSys is None:
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
403
|
+
# We need to have a default LangSys to attach variations to.
|
|
404
|
+
langSys = ot.LangSys()
|
|
405
|
+
langSys.LookupOrder = None
|
|
406
|
+
langSys.ReqFeatureIndex = 0xFFFF
|
|
407
|
+
langSys.FeatureIndex = []
|
|
408
|
+
langSys.FeatureCount = 0
|
|
409
|
+
scriptRecord.Script.DefaultLangSys = langSys
|
|
399
410
|
langSystems = [lsr.LangSys for lsr in scriptRecord.Script.LangSysRecord]
|
|
400
411
|
for langSys in [scriptRecord.Script.DefaultLangSys] + langSystems:
|
|
401
412
|
langSys.FeatureIndex.append(varFeatureIndex)
|
|
@@ -597,9 +608,12 @@ def buildFeatureRecord(featureTag, lookupListIndices):
|
|
|
597
608
|
def buildFeatureVariationRecord(conditionTable, substitutionRecords):
|
|
598
609
|
"""Build a FeatureVariationRecord."""
|
|
599
610
|
fvr = ot.FeatureVariationRecord()
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
611
|
+
if len(conditionTable) != 0:
|
|
612
|
+
fvr.ConditionSet = ot.ConditionSet()
|
|
613
|
+
fvr.ConditionSet.ConditionTable = conditionTable
|
|
614
|
+
fvr.ConditionSet.ConditionCount = len(conditionTable)
|
|
615
|
+
else:
|
|
616
|
+
fvr.ConditionSet = None
|
|
603
617
|
fvr.FeatureTableSubstitution = ot.FeatureTableSubstitution()
|
|
604
618
|
fvr.FeatureTableSubstitution.Version = 0x00010000
|
|
605
619
|
fvr.FeatureTableSubstitution.SubstitutionRecord = substitutionRecords
|
fontTools/varLib/hvar.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from fontTools.misc.roundTools import noRound
|
|
2
|
+
from fontTools.ttLib import TTFont, newTable
|
|
3
|
+
from fontTools.ttLib.tables import otTables as ot
|
|
4
|
+
from fontTools.ttLib.tables.otBase import OTTableWriter
|
|
5
|
+
from fontTools.varLib import HVAR_FIELDS, VVAR_FIELDS, _add_VHVAR
|
|
6
|
+
from fontTools.varLib import builder, models, varStore
|
|
7
|
+
from fontTools.misc.fixedTools import fixedToFloat as fi2fl
|
|
8
|
+
from fontTools.misc.cliTools import makeOutputFileName
|
|
9
|
+
from functools import partial
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger("fontTools.varLib.avar")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_advance_metrics(font, axisTags, tableFields):
|
|
16
|
+
# There's two ways we can go from here:
|
|
17
|
+
# 1. For each glyph, at each master peak, compute the value of the
|
|
18
|
+
# advance width at that peak. Then pass these all to a VariationModel
|
|
19
|
+
# builder to compute back the deltas.
|
|
20
|
+
# 2. For each master peak, pull out the deltas of the advance width directly,
|
|
21
|
+
# and feed these to the VarStoreBuilder, forgoing the remodeling step.
|
|
22
|
+
# We'll go with the second option, as it's simpler, faster, and more direct.
|
|
23
|
+
gvar = font["gvar"]
|
|
24
|
+
vhAdvanceDeltasAndSupports = {}
|
|
25
|
+
glyphOrder = font.getGlyphOrder()
|
|
26
|
+
phantomIndex = tableFields.phantomIndex
|
|
27
|
+
for glyphName in glyphOrder:
|
|
28
|
+
supports = []
|
|
29
|
+
deltas = []
|
|
30
|
+
variations = gvar.variations.get(glyphName, [])
|
|
31
|
+
|
|
32
|
+
for tv in variations:
|
|
33
|
+
supports.append(tv.axes)
|
|
34
|
+
phantoms = tv.coordinates[-4:]
|
|
35
|
+
phantoms = phantoms[phantomIndex * 2 : phantomIndex * 2 + 2]
|
|
36
|
+
assert len(phantoms) == 2
|
|
37
|
+
phantoms[0] = phantoms[0][phantomIndex] if phantoms[0] is not None else 0
|
|
38
|
+
phantoms[1] = phantoms[1][phantomIndex] if phantoms[1] is not None else 0
|
|
39
|
+
deltas.append(phantoms[1] - phantoms[0])
|
|
40
|
+
|
|
41
|
+
vhAdvanceDeltasAndSupports[glyphName] = (deltas, supports)
|
|
42
|
+
|
|
43
|
+
vOrigDeltasAndSupports = None # TODO
|
|
44
|
+
|
|
45
|
+
return vhAdvanceDeltasAndSupports, vOrigDeltasAndSupports
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def add_HVAR(font):
|
|
49
|
+
if "HVAR" in font:
|
|
50
|
+
del font["HVAR"]
|
|
51
|
+
axisTags = [axis.axisTag for axis in font["fvar"].axes]
|
|
52
|
+
getAdvanceMetrics = partial(_get_advance_metrics, font, axisTags, HVAR_FIELDS)
|
|
53
|
+
_add_VHVAR(font, axisTags, HVAR_FIELDS, getAdvanceMetrics)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def add_VVAR(font):
|
|
57
|
+
if "VVAR" in font:
|
|
58
|
+
del font["VVAR"]
|
|
59
|
+
getAdvanceMetrics = partial(_get_advance_metrics, font, axisTags, VVAR_FIELDS)
|
|
60
|
+
axisTags = [axis.axisTag for axis in font["fvar"].axes]
|
|
61
|
+
_add_VHVAR(font, axisTags, VVAR_FIELDS, getAdvanceMetrics)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main(args=None):
|
|
65
|
+
"""Add `HVAR` table to variable font."""
|
|
66
|
+
|
|
67
|
+
if args is None:
|
|
68
|
+
import sys
|
|
69
|
+
|
|
70
|
+
args = sys.argv[1:]
|
|
71
|
+
|
|
72
|
+
from fontTools import configLogger
|
|
73
|
+
from fontTools.designspaceLib import DesignSpaceDocument
|
|
74
|
+
import argparse
|
|
75
|
+
|
|
76
|
+
parser = argparse.ArgumentParser(
|
|
77
|
+
"fonttools varLib.hvar",
|
|
78
|
+
description="Add `HVAR` table from to variable font.",
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument("font", metavar="varfont.ttf", help="Variable-font file.")
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"-o",
|
|
83
|
+
"--output-file",
|
|
84
|
+
type=str,
|
|
85
|
+
help="Output font file name.",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
options = parser.parse_args(args)
|
|
89
|
+
|
|
90
|
+
configLogger(level="WARNING")
|
|
91
|
+
|
|
92
|
+
font = TTFont(options.font)
|
|
93
|
+
if not "fvar" in font:
|
|
94
|
+
log.error("Not a variable font.")
|
|
95
|
+
return 1
|
|
96
|
+
|
|
97
|
+
add_HVAR(font)
|
|
98
|
+
if "vmtx" in font:
|
|
99
|
+
add_VVAR(font)
|
|
100
|
+
|
|
101
|
+
if options.output_file is None:
|
|
102
|
+
outfile = makeOutputFileName(options.font, overWrite=True, suffix=".hvar")
|
|
103
|
+
else:
|
|
104
|
+
outfile = options.output_file
|
|
105
|
+
if outfile:
|
|
106
|
+
log.info("Saving %s", outfile)
|
|
107
|
+
font.save(outfile)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
import sys
|
|
112
|
+
|
|
113
|
+
sys.exit(main())
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Partially instantiate a variable font.
|
|
2
2
|
|
|
3
3
|
The module exports an `instantiateVariableFont` function and CLI that allow to
|
|
4
4
|
create full instances (i.e. static fonts) from variable fonts, as well as "partial"
|
|
@@ -36,7 +36,7 @@ If the input location specifies all the axes, the resulting instance is no longe
|
|
|
36
36
|
'variable' (same as using fontools varLib.mutator):
|
|
37
37
|
.. code-block:: pycon
|
|
38
38
|
|
|
39
|
-
>>>
|
|
39
|
+
>>>
|
|
40
40
|
>> instance = instancer.instantiateVariableFont(
|
|
41
41
|
... varfont, {"wght": 700, "wdth": 67.5}
|
|
42
42
|
... )
|
|
@@ -56,8 +56,10 @@ From the console script, this is equivalent to passing `wght=drop` as input.
|
|
|
56
56
|
|
|
57
57
|
This module is similar to fontTools.varLib.mutator, which it's intended to supersede.
|
|
58
58
|
Note that, unlike varLib.mutator, when an axis is not mentioned in the input
|
|
59
|
-
location, the varLib.instancer will keep the axis and the corresponding
|
|
60
|
-
whereas mutator implicitly drops the axis at its default coordinate.
|
|
59
|
+
location, by default the varLib.instancer will keep the axis and the corresponding
|
|
60
|
+
deltas, whereas mutator implicitly drops the axis at its default coordinate.
|
|
61
|
+
To obtain the same behavior as mutator, pass the `static=True` parameter or
|
|
62
|
+
the `--static` CLI option.
|
|
61
63
|
|
|
62
64
|
The module supports all the following "levels" of instancing, which can of
|
|
63
65
|
course be combined:
|
|
@@ -72,7 +74,7 @@ L1
|
|
|
72
74
|
L2
|
|
73
75
|
dropping one or more axes while pinning them at non-default locations;
|
|
74
76
|
.. code-block:: pycon
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
>>>
|
|
77
79
|
>> font = instancer.instantiateVariableFont(varfont, {"wght": 700})
|
|
78
80
|
|
|
@@ -81,22 +83,18 @@ L3
|
|
|
81
83
|
a new minimum or maximum, potentially -- though not necessarily -- dropping
|
|
82
84
|
entire regions of variations that fall completely outside this new range.
|
|
83
85
|
.. code-block:: pycon
|
|
84
|
-
|
|
86
|
+
|
|
85
87
|
>>>
|
|
86
88
|
>> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300)})
|
|
87
89
|
|
|
88
90
|
L4
|
|
89
91
|
moving the default location of an axis, by specifying (min,defalt,max) values:
|
|
90
92
|
.. code-block:: pycon
|
|
91
|
-
|
|
93
|
+
|
|
92
94
|
>>>
|
|
93
95
|
>> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300, 700)})
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
are supported, but support for CFF2 variable fonts will be added soon.
|
|
97
|
-
|
|
98
|
-
The discussion and implementation of these features are tracked at
|
|
99
|
-
https://github.com/fonttools/fonttools/issues/1537
|
|
97
|
+
Both TrueType-flavored (glyf+gvar) variable and CFF2 variable fonts are supported.
|
|
100
98
|
"""
|
|
101
99
|
|
|
102
100
|
from fontTools.misc.fixedTools import (
|
|
@@ -120,6 +118,7 @@ from fontTools.cffLib.specializer import (
|
|
|
120
118
|
specializeCommands,
|
|
121
119
|
generalizeCommands,
|
|
122
120
|
)
|
|
121
|
+
from fontTools.cffLib.CFF2ToCFF import convertCFF2ToCFF
|
|
123
122
|
from fontTools.varLib import builder
|
|
124
123
|
from fontTools.varLib.mvar import MVAR_ENTRIES
|
|
125
124
|
from fontTools.varLib.merger import MutatorMerger
|
|
@@ -136,6 +135,7 @@ from enum import IntEnum
|
|
|
136
135
|
import logging
|
|
137
136
|
import os
|
|
138
137
|
import re
|
|
138
|
+
import io
|
|
139
139
|
from typing import Dict, Iterable, Mapping, Optional, Sequence, Tuple, Union
|
|
140
140
|
import warnings
|
|
141
141
|
|
|
@@ -433,7 +433,27 @@ class AxisLimits(_BaseAxisLimits):
|
|
|
433
433
|
|
|
434
434
|
avarSegments = {}
|
|
435
435
|
if usingAvar and "avar" in varfont:
|
|
436
|
-
|
|
436
|
+
avar = varfont["avar"]
|
|
437
|
+
avarSegments = avar.segments
|
|
438
|
+
|
|
439
|
+
if getattr(avar, "majorVersion", 1) >= 2 and avar.table.VarStore:
|
|
440
|
+
pinnedAxes = set(self.pinnedLocation())
|
|
441
|
+
if not pinnedAxes.issuperset(avarSegments):
|
|
442
|
+
raise NotImplementedError(
|
|
443
|
+
"Partial-instancing avar2 table is not supported"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# TODO: Merge this with the main codepath.
|
|
447
|
+
|
|
448
|
+
# Full instancing of avar2 font. Use avar table to normalize location and return.
|
|
449
|
+
location = self.pinnedLocation()
|
|
450
|
+
location = {
|
|
451
|
+
tag: normalize(value, axes[tag], None)
|
|
452
|
+
for tag, value in location.items()
|
|
453
|
+
}
|
|
454
|
+
return NormalizedAxisLimits(
|
|
455
|
+
**avar.renormalizeLocation(location, varfont, dropZeroes=False)
|
|
456
|
+
)
|
|
437
457
|
|
|
438
458
|
normalizedLimits = {}
|
|
439
459
|
|
|
@@ -643,7 +663,11 @@ def instantiateCFF2(
|
|
|
643
663
|
# the Private dicts.
|
|
644
664
|
#
|
|
645
665
|
# Then prune unused things and possibly drop the VarStore if it's empty.
|
|
646
|
-
#
|
|
666
|
+
#
|
|
667
|
+
# If the downgrade parameter is True, no actual downgrading is done, but
|
|
668
|
+
# the function returns True if the VarStore was empty after instantiation,
|
|
669
|
+
# and hence a downgrade to CFF is possible. In all other cases it returns
|
|
670
|
+
# False.
|
|
647
671
|
|
|
648
672
|
log.info("Instantiating CFF2 table")
|
|
649
673
|
|
|
@@ -675,6 +699,7 @@ def instantiateCFF2(
|
|
|
675
699
|
privateDicts.append(fd.Private)
|
|
676
700
|
|
|
677
701
|
allCommands = []
|
|
702
|
+
allCommandPrivates = []
|
|
678
703
|
for cs in charStrings:
|
|
679
704
|
assert cs.private.vstore.otVarStore is varStore # Or in many places!!
|
|
680
705
|
commands = programToCommands(cs.program, getNumRegions=getNumRegions)
|
|
@@ -683,6 +708,7 @@ def instantiateCFF2(
|
|
|
683
708
|
if specialize:
|
|
684
709
|
commands = specializeCommands(commands, generalizeFirst=not generalize)
|
|
685
710
|
allCommands.append(commands)
|
|
711
|
+
allCommandPrivates.append(cs.private)
|
|
686
712
|
|
|
687
713
|
def storeBlendsToVarStore(arg):
|
|
688
714
|
if not isinstance(arg, list):
|
|
@@ -721,9 +747,7 @@ def instantiateCFF2(
|
|
|
721
747
|
minor = varDataCursor[major]
|
|
722
748
|
varDataCursor[major] += 1
|
|
723
749
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
defaultValue += round(defaultDeltas[varIdx])
|
|
750
|
+
defaultValue += round(defaultDeltas[major][minor])
|
|
727
751
|
newDefaults.append(defaultValue)
|
|
728
752
|
|
|
729
753
|
varData = varStore.VarData[major]
|
|
@@ -742,8 +766,8 @@ def instantiateCFF2(
|
|
|
742
766
|
assert varData.ItemCount == 0
|
|
743
767
|
|
|
744
768
|
# Add charstring blend lists to VarStore so we can instantiate them
|
|
745
|
-
for commands in allCommands:
|
|
746
|
-
vsindex = 0
|
|
769
|
+
for commands, private in zip(allCommands, allCommandPrivates):
|
|
770
|
+
vsindex = getattr(private, "vsindex", 0)
|
|
747
771
|
for command in commands:
|
|
748
772
|
if command[0] == "vsindex":
|
|
749
773
|
vsindex = command[1][0]
|
|
@@ -752,7 +776,6 @@ def instantiateCFF2(
|
|
|
752
776
|
storeBlendsToVarStore(arg)
|
|
753
777
|
|
|
754
778
|
# Add private blend lists to VarStore so we can instantiate values
|
|
755
|
-
vsindex = 0
|
|
756
779
|
for opcode, name, arg_type, default, converter in privateDictOperators2:
|
|
757
780
|
if arg_type not in ("number", "delta", "array"):
|
|
758
781
|
continue
|
|
@@ -763,6 +786,7 @@ def instantiateCFF2(
|
|
|
763
786
|
continue
|
|
764
787
|
values = getattr(private, name)
|
|
765
788
|
|
|
789
|
+
# This is safe here since "vsindex" is the first in the privateDictOperators2
|
|
766
790
|
if name == "vsindex":
|
|
767
791
|
vsindex = values[0]
|
|
768
792
|
continue
|
|
@@ -779,12 +803,14 @@ def instantiateCFF2(
|
|
|
779
803
|
storeBlendsToVarStore(value + [count])
|
|
780
804
|
|
|
781
805
|
# Instantiate VarStore
|
|
782
|
-
defaultDeltas = instantiateItemVariationStore(
|
|
806
|
+
defaultDeltas = instantiateItemVariationStore(
|
|
807
|
+
varStore, fvarAxes, axisLimits, hierarchical=True
|
|
808
|
+
)
|
|
783
809
|
|
|
784
810
|
# Read back new charstring blends from the instantiated VarStore
|
|
785
811
|
varDataCursor = [0] * len(varStore.VarData)
|
|
786
|
-
for commands in allCommands:
|
|
787
|
-
vsindex = 0
|
|
812
|
+
for commands, private in zip(allCommands, allCommandPrivates):
|
|
813
|
+
vsindex = getattr(private, "vsindex", 0)
|
|
788
814
|
for command in commands:
|
|
789
815
|
if command[0] == "vsindex":
|
|
790
816
|
vsindex = command[1][0]
|
|
@@ -799,9 +825,16 @@ def instantiateCFF2(
|
|
|
799
825
|
if arg_type not in ("number", "delta", "array"):
|
|
800
826
|
continue
|
|
801
827
|
|
|
828
|
+
vsindex = 0
|
|
802
829
|
for private in privateDicts:
|
|
803
830
|
if not hasattr(private, name):
|
|
804
831
|
continue
|
|
832
|
+
|
|
833
|
+
# This is safe here since "vsindex" is the first in the privateDictOperators2
|
|
834
|
+
if name == "vsindex":
|
|
835
|
+
vsindex = values[0]
|
|
836
|
+
continue
|
|
837
|
+
|
|
805
838
|
values = getattr(private, name)
|
|
806
839
|
if arg_type == "number":
|
|
807
840
|
values = [values]
|
|
@@ -830,18 +863,13 @@ def instantiateCFF2(
|
|
|
830
863
|
varData.Item = []
|
|
831
864
|
varData.ItemCount = 0
|
|
832
865
|
|
|
833
|
-
#
|
|
834
|
-
usedVsindex = set(
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
vsindex = command[1][0]
|
|
841
|
-
continue
|
|
842
|
-
if any(isinstance(arg, list) for arg in command[1]):
|
|
843
|
-
usedVsindex.add(vsindex)
|
|
844
|
-
else:
|
|
866
|
+
# Collect surviving vsindexes
|
|
867
|
+
usedVsindex = set(
|
|
868
|
+
i for i in range(len(varStore.VarData)) if varStore.VarData[i].VarRegionCount
|
|
869
|
+
)
|
|
870
|
+
# Remove vsindex commands that are no longer needed
|
|
871
|
+
for commands, private in zip(allCommands, allCommandPrivates):
|
|
872
|
+
if not any(isinstance(arg, list) for command in commands for arg in command[1]):
|
|
845
873
|
commands[:] = [command for command in commands if command[0] != "vsindex"]
|
|
846
874
|
|
|
847
875
|
# Remove unused VarData and update vsindex values
|
|
@@ -854,10 +882,14 @@ def instantiateCFF2(
|
|
|
854
882
|
for command in commands:
|
|
855
883
|
if command[0] == "vsindex":
|
|
856
884
|
command[1][0] = vsindexMapping[command[1][0]]
|
|
885
|
+
for private in privateDicts:
|
|
886
|
+
if hasattr(private, "vsindex"):
|
|
887
|
+
private.vsindex = vsindexMapping[private.vsindex]
|
|
857
888
|
|
|
858
889
|
# Remove initial vsindex commands that are implied
|
|
859
|
-
for commands in allCommands:
|
|
860
|
-
|
|
890
|
+
for commands, private in zip(allCommands, allCommandPrivates):
|
|
891
|
+
vsindex = getattr(private, "vsindex", 0)
|
|
892
|
+
if commands and commands[0] == ("vsindex", [vsindex]):
|
|
861
893
|
commands.pop(0)
|
|
862
894
|
|
|
863
895
|
# Ship the charstrings!
|
|
@@ -874,9 +906,9 @@ def instantiateCFF2(
|
|
|
874
906
|
del private.vstore
|
|
875
907
|
|
|
876
908
|
if downgrade:
|
|
877
|
-
|
|
909
|
+
return True
|
|
878
910
|
|
|
879
|
-
|
|
911
|
+
return False
|
|
880
912
|
|
|
881
913
|
|
|
882
914
|
def _instantiateGvarGlyph(
|
|
@@ -1108,7 +1140,8 @@ def _instantiateVHVAR(varfont, axisLimits, tableFields, *, round=round):
|
|
|
1108
1140
|
varIdx = advMapping.mapping[glyphName]
|
|
1109
1141
|
else:
|
|
1110
1142
|
varIdx = varfont.getGlyphID(glyphName)
|
|
1111
|
-
|
|
1143
|
+
delta = round(defaultDeltas[varIdx])
|
|
1144
|
+
metrics[glyphName] = (max(0, advanceWidth + delta), sb)
|
|
1112
1145
|
|
|
1113
1146
|
if (
|
|
1114
1147
|
tableTag == "VVAR"
|
|
@@ -1238,7 +1271,9 @@ class _TupleVarStoreAdapter(object):
|
|
|
1238
1271
|
return itemVarStore
|
|
1239
1272
|
|
|
1240
1273
|
|
|
1241
|
-
def instantiateItemVariationStore(
|
|
1274
|
+
def instantiateItemVariationStore(
|
|
1275
|
+
itemVarStore, fvarAxes, axisLimits, hierarchical=False
|
|
1276
|
+
):
|
|
1242
1277
|
"""Compute deltas at partial location, and update varStore in-place.
|
|
1243
1278
|
|
|
1244
1279
|
Remove regions in which all axes were instanced, or fall outside the new axis
|
|
@@ -1270,12 +1305,19 @@ def instantiateItemVariationStore(itemVarStore, fvarAxes, axisLimits):
|
|
|
1270
1305
|
assert itemVarStore.VarDataCount == newItemVarStore.VarDataCount
|
|
1271
1306
|
itemVarStore.VarData = newItemVarStore.VarData
|
|
1272
1307
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1308
|
+
if not hierarchical:
|
|
1309
|
+
defaultDeltas = {
|
|
1310
|
+
((major << 16) + minor): delta
|
|
1311
|
+
for major, deltas in enumerate(defaultDeltaArray)
|
|
1312
|
+
for minor, delta in enumerate(deltas)
|
|
1313
|
+
}
|
|
1314
|
+
defaultDeltas[itemVarStore.NO_VARIATION_INDEX] = 0
|
|
1315
|
+
else:
|
|
1316
|
+
defaultDeltas = {0xFFFF: {0xFFFF: 0}} # NO_VARIATION_INDEX
|
|
1317
|
+
for major, deltas in enumerate(defaultDeltaArray):
|
|
1318
|
+
defaultDeltasForMajor = defaultDeltas.setdefault(major, {})
|
|
1319
|
+
for minor, delta in enumerate(deltas):
|
|
1320
|
+
defaultDeltasForMajor[minor] = delta
|
|
1279
1321
|
return defaultDeltas
|
|
1280
1322
|
|
|
1281
1323
|
|
|
@@ -1360,12 +1402,55 @@ def _isValidAvarSegmentMap(axisTag, segmentMap):
|
|
|
1360
1402
|
return True
|
|
1361
1403
|
|
|
1362
1404
|
|
|
1405
|
+
def downgradeCFF2ToCFF(varfont):
|
|
1406
|
+
# Save these properties
|
|
1407
|
+
recalcTimestamp = varfont.recalcTimestamp
|
|
1408
|
+
recalcBBoxes = varfont.recalcBBoxes
|
|
1409
|
+
|
|
1410
|
+
# Disable them
|
|
1411
|
+
varfont.recalcTimestamp = False
|
|
1412
|
+
varfont.recalcBBoxes = False
|
|
1413
|
+
|
|
1414
|
+
# Save to memory, reload, downgrade and save again, reload.
|
|
1415
|
+
# We do this dance because the convertCFF2ToCFF changes glyph
|
|
1416
|
+
# names, so following save would fail if any other table was
|
|
1417
|
+
# loaded and referencing glyph names.
|
|
1418
|
+
#
|
|
1419
|
+
# The second save+load is unfortunate but also necessary.
|
|
1420
|
+
|
|
1421
|
+
stream = io.BytesIO()
|
|
1422
|
+
log.info("Saving CFF2 font to memory for downgrade")
|
|
1423
|
+
varfont.save(stream)
|
|
1424
|
+
stream.seek(0)
|
|
1425
|
+
varfont = TTFont(stream, recalcTimestamp=False, recalcBBoxes=False)
|
|
1426
|
+
|
|
1427
|
+
convertCFF2ToCFF(varfont)
|
|
1428
|
+
|
|
1429
|
+
stream = io.BytesIO()
|
|
1430
|
+
log.info("Saving downgraded CFF font to memory")
|
|
1431
|
+
varfont.save(stream)
|
|
1432
|
+
stream.seek(0)
|
|
1433
|
+
varfont = TTFont(stream, recalcTimestamp=False, recalcBBoxes=False)
|
|
1434
|
+
|
|
1435
|
+
# Uncomment, to see test all tables can be loaded. This fails without
|
|
1436
|
+
# the extra save+load above.
|
|
1437
|
+
"""
|
|
1438
|
+
for tag in varfont.keys():
|
|
1439
|
+
print("Loading", tag)
|
|
1440
|
+
varfont[tag]
|
|
1441
|
+
"""
|
|
1442
|
+
|
|
1443
|
+
# Restore them
|
|
1444
|
+
varfont.recalcTimestamp = recalcTimestamp
|
|
1445
|
+
varfont.recalcBBoxes = recalcBBoxes
|
|
1446
|
+
|
|
1447
|
+
return varfont
|
|
1448
|
+
|
|
1449
|
+
|
|
1363
1450
|
def instantiateAvar(varfont, axisLimits):
|
|
1364
1451
|
# 'axisLimits' dict must contain user-space (non-normalized) coordinates.
|
|
1365
1452
|
|
|
1366
1453
|
avar = varfont["avar"]
|
|
1367
|
-
if getattr(avar, "majorVersion", 1) >= 2 and avar.table.VarStore:
|
|
1368
|
-
raise NotImplementedError("avar table with VarStore is not supported")
|
|
1369
1454
|
|
|
1370
1455
|
segments = avar.segments
|
|
1371
1456
|
|
|
@@ -1376,6 +1461,9 @@ def instantiateAvar(varfont, axisLimits):
|
|
|
1376
1461
|
del varfont["avar"]
|
|
1377
1462
|
return
|
|
1378
1463
|
|
|
1464
|
+
if getattr(avar, "majorVersion", 1) >= 2 and avar.table.VarStore:
|
|
1465
|
+
raise NotImplementedError("avar table with VarStore is not supported")
|
|
1466
|
+
|
|
1379
1467
|
log.info("Instantiating avar table")
|
|
1380
1468
|
for axis in pinnedAxes:
|
|
1381
1469
|
if axis in segments:
|
|
@@ -1577,6 +1665,7 @@ def instantiateVariableFont(
|
|
|
1577
1665
|
updateFontNames=False,
|
|
1578
1666
|
*,
|
|
1579
1667
|
downgradeCFF2=False,
|
|
1668
|
+
static=False,
|
|
1580
1669
|
):
|
|
1581
1670
|
"""Instantiate variable font, either fully or partially.
|
|
1582
1671
|
|
|
@@ -1620,12 +1709,23 @@ def instantiateVariableFont(
|
|
|
1620
1709
|
software that does not support CFF2. Defaults to False. Note that this
|
|
1621
1710
|
operation also removes overlaps within glyph shapes, as CFF does not support
|
|
1622
1711
|
overlaps but CFF2 does.
|
|
1712
|
+
static (bool): if True, generate a full instance (static font) instead of a partial
|
|
1713
|
+
instance (variable font).
|
|
1623
1714
|
"""
|
|
1624
1715
|
# 'overlap' used to be bool and is now enum; for backward compat keep accepting bool
|
|
1625
1716
|
overlap = OverlapMode(int(overlap))
|
|
1626
1717
|
|
|
1627
1718
|
sanityCheckVariableTables(varfont)
|
|
1628
1719
|
|
|
1720
|
+
if static:
|
|
1721
|
+
unspecified = []
|
|
1722
|
+
for axis in varfont["fvar"].axes:
|
|
1723
|
+
if axis.axisTag not in axisLimits:
|
|
1724
|
+
axisLimits[axis.axisTag] = None
|
|
1725
|
+
unspecified.append(axis.axisTag)
|
|
1726
|
+
if unspecified:
|
|
1727
|
+
log.info("Pinning unspecified axes to default: %s", unspecified)
|
|
1728
|
+
|
|
1629
1729
|
axisLimits = AxisLimits(axisLimits).limitAxesAndPopulateDefaults(varfont)
|
|
1630
1730
|
|
|
1631
1731
|
log.info("Restricted limits: %s", axisLimits)
|
|
@@ -1648,7 +1748,9 @@ def instantiateVariableFont(
|
|
|
1648
1748
|
instantiateVARC(varfont, normalizedLimits)
|
|
1649
1749
|
|
|
1650
1750
|
if "CFF2" in varfont:
|
|
1651
|
-
instantiateCFF2(
|
|
1751
|
+
downgradeCFF2 = instantiateCFF2(
|
|
1752
|
+
varfont, normalizedLimits, downgrade=downgradeCFF2
|
|
1753
|
+
)
|
|
1652
1754
|
|
|
1653
1755
|
if "gvar" in varfont:
|
|
1654
1756
|
instantiateGvar(varfont, normalizedLimits, optimize=optimize)
|
|
@@ -1678,19 +1780,6 @@ def instantiateVariableFont(
|
|
|
1678
1780
|
|
|
1679
1781
|
instantiateFvar(varfont, axisLimits)
|
|
1680
1782
|
|
|
1681
|
-
if "fvar" not in varfont:
|
|
1682
|
-
if "glyf" in varfont:
|
|
1683
|
-
if overlap == OverlapMode.KEEP_AND_SET_FLAGS:
|
|
1684
|
-
setMacOverlapFlags(varfont["glyf"])
|
|
1685
|
-
elif overlap in (OverlapMode.REMOVE, OverlapMode.REMOVE_AND_IGNORE_ERRORS):
|
|
1686
|
-
from fontTools.ttLib.removeOverlaps import removeOverlaps
|
|
1687
|
-
|
|
1688
|
-
log.info("Removing overlaps from glyf table")
|
|
1689
|
-
removeOverlaps(
|
|
1690
|
-
varfont,
|
|
1691
|
-
ignoreErrors=(overlap == OverlapMode.REMOVE_AND_IGNORE_ERRORS),
|
|
1692
|
-
)
|
|
1693
|
-
|
|
1694
1783
|
if "OS/2" in varfont:
|
|
1695
1784
|
varfont["OS/2"].recalcAvgCharWidth(varfont)
|
|
1696
1785
|
|
|
@@ -1703,6 +1792,25 @@ def instantiateVariableFont(
|
|
|
1703
1792
|
# name table has been updated.
|
|
1704
1793
|
setRibbiBits(varfont)
|
|
1705
1794
|
|
|
1795
|
+
if downgradeCFF2:
|
|
1796
|
+
origVarfont = varfont
|
|
1797
|
+
varfont = downgradeCFF2ToCFF(varfont)
|
|
1798
|
+
if inplace:
|
|
1799
|
+
origVarfont.__dict__ = varfont.__dict__.copy()
|
|
1800
|
+
|
|
1801
|
+
if "fvar" not in varfont:
|
|
1802
|
+
if overlap == OverlapMode.KEEP_AND_SET_FLAGS:
|
|
1803
|
+
if "glyf" in varfont:
|
|
1804
|
+
setMacOverlapFlags(varfont["glyf"])
|
|
1805
|
+
elif overlap in (OverlapMode.REMOVE, OverlapMode.REMOVE_AND_IGNORE_ERRORS):
|
|
1806
|
+
from fontTools.ttLib.removeOverlaps import removeOverlaps
|
|
1807
|
+
|
|
1808
|
+
log.info("Removing glyph outlines overlaps")
|
|
1809
|
+
removeOverlaps(
|
|
1810
|
+
varfont,
|
|
1811
|
+
ignoreErrors=(overlap == OverlapMode.REMOVE_AND_IGNORE_ERRORS),
|
|
1812
|
+
)
|
|
1813
|
+
|
|
1706
1814
|
return varfont
|
|
1707
1815
|
|
|
1708
1816
|
|
|
@@ -1809,6 +1917,12 @@ def parseArgs(args):
|
|
|
1809
1917
|
default=None,
|
|
1810
1918
|
help="Output instance TTF file (default: INPUT-instance.ttf).",
|
|
1811
1919
|
)
|
|
1920
|
+
parser.add_argument(
|
|
1921
|
+
"--static",
|
|
1922
|
+
dest="static",
|
|
1923
|
+
action="store_true",
|
|
1924
|
+
help="Make a static font: pin unspecified axes to their default location.",
|
|
1925
|
+
)
|
|
1812
1926
|
parser.add_argument(
|
|
1813
1927
|
"--no-optimize",
|
|
1814
1928
|
dest="optimize",
|
|
@@ -1906,13 +2020,13 @@ def main(args=None):
|
|
|
1906
2020
|
recalcBBoxes=options.recalc_bounds,
|
|
1907
2021
|
)
|
|
1908
2022
|
|
|
1909
|
-
isFullInstance = {
|
|
2023
|
+
isFullInstance = options.static or {
|
|
1910
2024
|
axisTag
|
|
1911
2025
|
for axisTag, limit in axisLimits.items()
|
|
1912
2026
|
if limit is None or limit[0] == limit[2]
|
|
1913
2027
|
}.issuperset(axis.axisTag for axis in varfont["fvar"].axes)
|
|
1914
2028
|
|
|
1915
|
-
instantiateVariableFont(
|
|
2029
|
+
varfont = instantiateVariableFont(
|
|
1916
2030
|
varfont,
|
|
1917
2031
|
axisLimits,
|
|
1918
2032
|
inplace=True,
|
|
@@ -1920,6 +2034,7 @@ def main(args=None):
|
|
|
1920
2034
|
overlap=options.overlap,
|
|
1921
2035
|
updateFontNames=options.update_name_table,
|
|
1922
2036
|
downgradeCFF2=options.downgrade_cff2,
|
|
2037
|
+
static=options.static,
|
|
1923
2038
|
)
|
|
1924
2039
|
|
|
1925
2040
|
suffix = "-instance" if isFullInstance else "-partial"
|
|
@@ -174,6 +174,9 @@ def min_cost_perfect_bipartite_matching_bruteforce(G):
|
|
|
174
174
|
return best, best_cost
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
# Prefer `scipy.optimize.linear_sum_assignment` for performance.
|
|
178
|
+
# `Munkres` is also supported as a fallback for minimalistic systems
|
|
179
|
+
# where installing SciPy is not feasible.
|
|
177
180
|
try:
|
|
178
181
|
from scipy.optimize import linear_sum_assignment
|
|
179
182
|
|