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
|
Binary file
|
fontTools/varLib/models.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Variation fonts interpolation models."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
__all__ = [
|
|
4
6
|
"normalizeValue",
|
|
5
7
|
"normalizeLocation",
|
|
@@ -8,10 +10,15 @@ __all__ = [
|
|
|
8
10
|
"VariationModel",
|
|
9
11
|
]
|
|
10
12
|
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
11
14
|
from fontTools.misc.roundTools import noRound
|
|
12
15
|
from .errors import VariationModelError
|
|
13
16
|
|
|
14
17
|
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from typing import Mapping, Sequence
|
|
20
|
+
|
|
21
|
+
|
|
15
22
|
def nonNone(lst):
|
|
16
23
|
return [l for l in lst if l is not None]
|
|
17
24
|
|
|
@@ -44,7 +51,9 @@ def subList(truth, lst):
|
|
|
44
51
|
return [l for l, t in zip(lst, truth) if t]
|
|
45
52
|
|
|
46
53
|
|
|
47
|
-
def normalizeValue(
|
|
54
|
+
def normalizeValue(
|
|
55
|
+
v: float, triple: Sequence[float], extrapolate: bool = False
|
|
56
|
+
) -> float:
|
|
48
57
|
"""Normalizes value based on a min/default/max triple.
|
|
49
58
|
|
|
50
59
|
>>> normalizeValue(400, (100, 400, 900))
|
|
@@ -75,7 +84,13 @@ def normalizeValue(v, triple, extrapolate=False):
|
|
|
75
84
|
return (v - default) / (upper - default)
|
|
76
85
|
|
|
77
86
|
|
|
78
|
-
def normalizeLocation(
|
|
87
|
+
def normalizeLocation(
|
|
88
|
+
location: Mapping[str, float],
|
|
89
|
+
axes: Mapping[str, tuple[float, float, float]],
|
|
90
|
+
extrapolate: bool = False,
|
|
91
|
+
*,
|
|
92
|
+
validate: bool = False,
|
|
93
|
+
) -> dict[str, float]:
|
|
79
94
|
"""Normalizes location based on axis min/default/max values from axes.
|
|
80
95
|
|
|
81
96
|
>>> axes = {"wght": (100, 400, 900)}
|
fontTools/varLib/mutator.py
CHANGED
|
@@ -4,9 +4,16 @@ Instantiate a variation font. Run, eg:
|
|
|
4
4
|
.. code-block:: sh
|
|
5
5
|
|
|
6
6
|
$ fonttools varLib.mutator ./NotoSansArabic-VF.ttf wght=140 wdth=85
|
|
7
|
+
|
|
8
|
+
.. warning::
|
|
9
|
+
``fontTools.varLib.mutator`` is deprecated in favor of :mod:`fontTools.varLib.instancer`
|
|
10
|
+
which provides equivalent full instancing and also supports partial instancing.
|
|
11
|
+
Please migrate CLI usage to ``fonttools varLib.instancer`` and API usage to
|
|
12
|
+
:func:`fontTools.varLib.instancer.instantiateVariableFont`.
|
|
7
13
|
"""
|
|
8
14
|
|
|
9
15
|
from fontTools.misc.fixedTools import floatToFixedToFloat, floatToFixed
|
|
16
|
+
from fontTools.misc.loggingTools import deprecateFunction
|
|
10
17
|
from fontTools.misc.roundTools import otRound
|
|
11
18
|
from fontTools.pens.boundsPen import BoundsPen
|
|
12
19
|
from fontTools.ttLib import TTFont, newTable
|
|
@@ -159,6 +166,10 @@ def interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc):
|
|
|
159
166
|
hmtx[gname] = tuple(entry)
|
|
160
167
|
|
|
161
168
|
|
|
169
|
+
@deprecateFunction(
|
|
170
|
+
"use fontTools.varLib.instancer.instantiateVariableFont instead "
|
|
171
|
+
"for either full or partial instancing",
|
|
172
|
+
)
|
|
162
173
|
def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
|
|
163
174
|
"""Generate a static instance from a variable TTFont and a dictionary
|
|
164
175
|
defining the desired location along the variable font's axes.
|
fontTools/varLib/varStore.py
CHANGED
|
@@ -41,7 +41,7 @@ class OnlineVarStoreBuilder(object):
|
|
|
41
41
|
def setSupports(self, supports):
|
|
42
42
|
self._model = None
|
|
43
43
|
self._supports = list(supports)
|
|
44
|
-
if not self._supports[0]:
|
|
44
|
+
if self._supports and not self._supports[0]:
|
|
45
45
|
del self._supports[0] # Drop base master support
|
|
46
46
|
self._cache = None
|
|
47
47
|
self._data = None
|
|
@@ -412,25 +412,6 @@ class _Encoding(object):
|
|
|
412
412
|
def extend(self, lst):
|
|
413
413
|
self.items.update(lst)
|
|
414
414
|
|
|
415
|
-
def get_room(self):
|
|
416
|
-
"""Maximum number of bytes that can be added to characteristic
|
|
417
|
-
while still being beneficial to merge it into another one."""
|
|
418
|
-
count = len(self.items)
|
|
419
|
-
return max(0, (self.overhead - 1) // count - self.width)
|
|
420
|
-
|
|
421
|
-
room = property(get_room)
|
|
422
|
-
|
|
423
|
-
def get_gain(self):
|
|
424
|
-
"""Maximum possible byte gain from merging this into another
|
|
425
|
-
characteristic."""
|
|
426
|
-
count = len(self.items)
|
|
427
|
-
return max(0, self.overhead - count)
|
|
428
|
-
|
|
429
|
-
gain = property(get_gain)
|
|
430
|
-
|
|
431
|
-
def gain_sort_key(self):
|
|
432
|
-
return self.gain, self.chars
|
|
433
|
-
|
|
434
415
|
def width_sort_key(self):
|
|
435
416
|
return self.width, self.chars
|
|
436
417
|
|
|
@@ -534,13 +515,9 @@ def VarStore_optimize(self, use_NO_VARIATION_INDEX=True, quantization=1):
|
|
|
534
515
|
# of the old encoding is completely eliminated. However, each row
|
|
535
516
|
# now would require more bytes to encode, to the tune of one byte
|
|
536
517
|
# per characteristic bit that is active in the new encoding but not
|
|
537
|
-
# in the old one.
|
|
538
|
-
# while still beneficial to merge it into another encoding is called
|
|
539
|
-
# the "room" for that encoding.
|
|
518
|
+
# in the old one.
|
|
540
519
|
#
|
|
541
|
-
# The "gain" of
|
|
542
|
-
# save by merging it into another encoding. The "gain" of merging
|
|
543
|
-
# two encodings is how many bytes we save by doing so.
|
|
520
|
+
# The "gain" of merging two encodings is how many bytes we save by doing so.
|
|
544
521
|
#
|
|
545
522
|
# High-level algorithm:
|
|
546
523
|
#
|
|
@@ -554,7 +531,11 @@ def VarStore_optimize(self, use_NO_VARIATION_INDEX=True, quantization=1):
|
|
|
554
531
|
#
|
|
555
532
|
# - Put all encodings into a "todo" list.
|
|
556
533
|
#
|
|
557
|
-
# - Sort todo list
|
|
534
|
+
# - Sort todo list (for stability) by width_sort_key(), which is a tuple
|
|
535
|
+
# of the following items:
|
|
536
|
+
# * The "width" of the encoding.
|
|
537
|
+
# * The characteristic bitmap of the encoding, with higher-numbered
|
|
538
|
+
# columns compared first.
|
|
558
539
|
#
|
|
559
540
|
# - Make a priority-queue of the gain from combining each two
|
|
560
541
|
# encodings in the todo list. The priority queue is sorted by
|
|
@@ -575,16 +556,7 @@ def VarStore_optimize(self, use_NO_VARIATION_INDEX=True, quantization=1):
|
|
|
575
556
|
#
|
|
576
557
|
# The output is then sorted for stability, in the following way:
|
|
577
558
|
# - The VarRegionList of the input is kept intact.
|
|
578
|
-
# -
|
|
579
|
-
# gain_key_sort(), which is a tuple of the following items:
|
|
580
|
-
# * The gain of the encoding.
|
|
581
|
-
# * The characteristic bitmap of the encoding, with higher-numbered
|
|
582
|
-
# columns compared first.
|
|
583
|
-
# - The VarData is sorted by width_sort_key(), which is a tuple
|
|
584
|
-
# of the following items:
|
|
585
|
-
# * The "width" of the encoding.
|
|
586
|
-
# * The characteristic bitmap of the encoding, with higher-numbered
|
|
587
|
-
# columns compared first.
|
|
559
|
+
# - The VarData is sorted by the same width_sort_key() used at the beginning.
|
|
588
560
|
# - Within each VarData, the items are sorted as vectors of numbers.
|
|
589
561
|
#
|
|
590
562
|
# Finally, each VarData is optimized to remove the empty columns and
|
|
@@ -626,7 +598,7 @@ def VarStore_optimize(self, use_NO_VARIATION_INDEX=True, quantization=1):
|
|
|
626
598
|
front_mapping[(major << 16) + minor] = row
|
|
627
599
|
|
|
628
600
|
# Prepare for the main algorithm.
|
|
629
|
-
todo = sorted(encodings.values(), key=_Encoding.
|
|
601
|
+
todo = sorted(encodings.values(), key=_Encoding.width_sort_key)
|
|
630
602
|
del encodings
|
|
631
603
|
|
|
632
604
|
# Repeatedly pick two best encodings to combine, and combine them.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from io import StringIO
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from fontTools import configLogger
|
|
8
|
+
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
|
|
9
|
+
from fontTools.feaLib.error import FeatureLibError
|
|
10
|
+
from fontTools.feaLib.lexer import Lexer
|
|
11
|
+
from fontTools.misc.cliTools import makeOutputFileName
|
|
12
|
+
from fontTools.ttLib import TTFont, TTLibError
|
|
13
|
+
from fontTools.voltLib.parser import Parser
|
|
14
|
+
from fontTools.voltLib.voltToFea import TABLES, VoltToFea
|
|
15
|
+
|
|
16
|
+
log = logging.getLogger("fontTools.feaLib")
|
|
17
|
+
|
|
18
|
+
SUPPORTED_TABLES = TABLES + ["cmap"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def invalid_fea_glyph_name(name):
|
|
22
|
+
"""Check if the glyph name is valid according to FEA syntax."""
|
|
23
|
+
if name[0] not in Lexer.CHAR_NAME_START_:
|
|
24
|
+
return True
|
|
25
|
+
if any(c not in Lexer.CHAR_NAME_CONTINUATION_ for c in name[1:]):
|
|
26
|
+
return True
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def sanitize_glyph_name(name):
|
|
31
|
+
"""Sanitize the glyph name to ensure it is valid according to FEA syntax."""
|
|
32
|
+
sanitized = ""
|
|
33
|
+
for i, c in enumerate(name):
|
|
34
|
+
if i == 0 and c not in Lexer.CHAR_NAME_START_:
|
|
35
|
+
sanitized += "a" + c
|
|
36
|
+
elif c not in Lexer.CHAR_NAME_CONTINUATION_:
|
|
37
|
+
sanitized += "_"
|
|
38
|
+
else:
|
|
39
|
+
sanitized += c
|
|
40
|
+
|
|
41
|
+
return sanitized
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main(args=None):
|
|
45
|
+
"""Build tables from a MS VOLT project into an OTF font"""
|
|
46
|
+
parser = argparse.ArgumentParser(
|
|
47
|
+
description="Use fontTools to compile MS VOLT projects."
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"input",
|
|
51
|
+
metavar="INPUT",
|
|
52
|
+
help="Path to the input font/VTP file to process",
|
|
53
|
+
type=Path,
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"-f",
|
|
57
|
+
"--font",
|
|
58
|
+
metavar="INPUT_FONT",
|
|
59
|
+
help="Path to the input font (if INPUT is a VTP file)",
|
|
60
|
+
type=Path,
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"-o",
|
|
64
|
+
"--output",
|
|
65
|
+
dest="output",
|
|
66
|
+
metavar="OUTPUT",
|
|
67
|
+
help="Path to the output font.",
|
|
68
|
+
type=Path,
|
|
69
|
+
)
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
"-t",
|
|
72
|
+
"--tables",
|
|
73
|
+
metavar="TABLE_TAG",
|
|
74
|
+
choices=SUPPORTED_TABLES,
|
|
75
|
+
nargs="+",
|
|
76
|
+
help="Specify the table(s) to be built.",
|
|
77
|
+
)
|
|
78
|
+
parser.add_argument(
|
|
79
|
+
"-F",
|
|
80
|
+
"--debug-feature-file",
|
|
81
|
+
help="Write the generated feature file to disk.",
|
|
82
|
+
action="store_true",
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
"--ship",
|
|
86
|
+
help="Remove source VOLT tables from output font.",
|
|
87
|
+
action="store_true",
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"-v",
|
|
91
|
+
"--verbose",
|
|
92
|
+
help="Increase the logger verbosity. Multiple -v options are allowed.",
|
|
93
|
+
action="count",
|
|
94
|
+
default=0,
|
|
95
|
+
)
|
|
96
|
+
parser.add_argument(
|
|
97
|
+
"-T",
|
|
98
|
+
"--traceback",
|
|
99
|
+
help="show traceback for exceptions.",
|
|
100
|
+
action="store_true",
|
|
101
|
+
)
|
|
102
|
+
options = parser.parse_args(args)
|
|
103
|
+
|
|
104
|
+
levels = ["WARNING", "INFO", "DEBUG"]
|
|
105
|
+
configLogger(level=levels[min(len(levels) - 1, options.verbose)])
|
|
106
|
+
|
|
107
|
+
output_font = options.output or Path(
|
|
108
|
+
makeOutputFileName(options.font or options.input)
|
|
109
|
+
)
|
|
110
|
+
log.info(f"Compiling MS VOLT to '{output_font}'")
|
|
111
|
+
|
|
112
|
+
file_or_path = options.input
|
|
113
|
+
font = None
|
|
114
|
+
|
|
115
|
+
# If the input is a font file, extract the VOLT data from the "TSIV" table
|
|
116
|
+
try:
|
|
117
|
+
font = TTFont(file_or_path)
|
|
118
|
+
if "TSIV" in font:
|
|
119
|
+
file_or_path = StringIO(font["TSIV"].data.decode("utf-8"))
|
|
120
|
+
else:
|
|
121
|
+
log.error('"TSIV" table is missing')
|
|
122
|
+
return 1
|
|
123
|
+
except TTLibError:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
# If input is not a font file, the font must be provided
|
|
127
|
+
if font is None:
|
|
128
|
+
if not options.font:
|
|
129
|
+
log.error("Please provide an input font")
|
|
130
|
+
return 1
|
|
131
|
+
font = TTFont(options.font)
|
|
132
|
+
|
|
133
|
+
# FEA syntax does not allow some glyph names that VOLT accepts, so if we
|
|
134
|
+
# found such glyph name we will temporarily rename such glyphs.
|
|
135
|
+
glyphOrder = font.getGlyphOrder()
|
|
136
|
+
tempGlyphOrder = None
|
|
137
|
+
if any(invalid_fea_glyph_name(n) for n in glyphOrder):
|
|
138
|
+
tempGlyphOrder = []
|
|
139
|
+
for n in glyphOrder:
|
|
140
|
+
if invalid_fea_glyph_name(n):
|
|
141
|
+
n = sanitize_glyph_name(n)
|
|
142
|
+
existing = set(tempGlyphOrder) | set(glyphOrder)
|
|
143
|
+
while n in existing:
|
|
144
|
+
n = "a" + n
|
|
145
|
+
tempGlyphOrder.append(n)
|
|
146
|
+
font.setGlyphOrder(tempGlyphOrder)
|
|
147
|
+
|
|
148
|
+
doc = Parser(file_or_path).parse()
|
|
149
|
+
|
|
150
|
+
log.info("Converting VTP data to FEA")
|
|
151
|
+
converter = VoltToFea(doc, font)
|
|
152
|
+
try:
|
|
153
|
+
fea = converter.convert(options.tables, ignore_unsupported_settings=True)
|
|
154
|
+
except NotImplementedError as e:
|
|
155
|
+
if options.traceback:
|
|
156
|
+
raise
|
|
157
|
+
location = getattr(e.args[0], "location", None)
|
|
158
|
+
message = f'"{e}" is not supported'
|
|
159
|
+
if location:
|
|
160
|
+
path, line, column = location
|
|
161
|
+
log.error(f"{path}:{line}:{column}: {message}")
|
|
162
|
+
else:
|
|
163
|
+
log.error(message)
|
|
164
|
+
return 1
|
|
165
|
+
|
|
166
|
+
fea_filename = options.input
|
|
167
|
+
if options.debug_feature_file:
|
|
168
|
+
fea_filename = output_font.with_suffix(".fea")
|
|
169
|
+
log.info(f"Writing FEA to '{fea_filename}'")
|
|
170
|
+
with open(fea_filename, "w") as fp:
|
|
171
|
+
fp.write(fea)
|
|
172
|
+
|
|
173
|
+
log.info("Compiling FEA to OpenType tables")
|
|
174
|
+
try:
|
|
175
|
+
addOpenTypeFeaturesFromString(
|
|
176
|
+
font,
|
|
177
|
+
fea,
|
|
178
|
+
filename=fea_filename,
|
|
179
|
+
tables=options.tables,
|
|
180
|
+
)
|
|
181
|
+
except FeatureLibError as e:
|
|
182
|
+
if options.traceback:
|
|
183
|
+
raise
|
|
184
|
+
log.error(e)
|
|
185
|
+
return 1
|
|
186
|
+
|
|
187
|
+
if options.ship:
|
|
188
|
+
for tag in ["TSIV", "TSIS", "TSIP", "TSID"]:
|
|
189
|
+
if tag in font:
|
|
190
|
+
del font[tag]
|
|
191
|
+
|
|
192
|
+
# Restore original glyph names.
|
|
193
|
+
if tempGlyphOrder:
|
|
194
|
+
import io
|
|
195
|
+
|
|
196
|
+
f = io.BytesIO()
|
|
197
|
+
font.save(f)
|
|
198
|
+
font = TTFont(f)
|
|
199
|
+
font.setGlyphOrder(glyphOrder)
|
|
200
|
+
font["post"].extraNames = []
|
|
201
|
+
|
|
202
|
+
font.save(output_font)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
if __name__ == "__main__":
|
|
206
|
+
sys.exit(main())
|
fontTools/voltLib/ast.py
CHANGED
|
@@ -317,6 +317,10 @@ class SubstitutionLigatureDefinition(SubstitutionDefinition):
|
|
|
317
317
|
pass
|
|
318
318
|
|
|
319
319
|
|
|
320
|
+
class SubstitutionAlternateDefinition(SubstitutionDefinition):
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
|
|
320
324
|
class SubstitutionReverseChainingSingleDefinition(SubstitutionDefinition):
|
|
321
325
|
pass
|
|
322
326
|
|
fontTools/voltLib/parser.py
CHANGED
|
@@ -313,19 +313,27 @@ class Parser(object):
|
|
|
313
313
|
self.expect_keyword_("END_SUBSTITUTION")
|
|
314
314
|
max_src = max([len(cov) for cov in src])
|
|
315
315
|
max_dest = max([len(cov) for cov in dest])
|
|
316
|
+
|
|
316
317
|
# many to many or mixed is invalid
|
|
317
|
-
if
|
|
318
|
-
reversal and (max_src > 1 or max_dest > 1)
|
|
319
|
-
):
|
|
318
|
+
if max_src > 1 and max_dest > 1:
|
|
320
319
|
raise VoltLibError("Invalid substitution type", location)
|
|
320
|
+
|
|
321
321
|
mapping = dict(zip(tuple(src), tuple(dest)))
|
|
322
322
|
if max_src == 1 and max_dest == 1:
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
# Alternate substitutions are represented by adding multiple
|
|
324
|
+
# substitutions for the same glyph, so we detect that here
|
|
325
|
+
glyphs = [x.glyphSet() for cov in src for x in cov] # flatten src
|
|
326
|
+
if len(set(glyphs)) != len(glyphs): # src has duplicates
|
|
327
|
+
sub = ast.SubstitutionAlternateDefinition(mapping, location=location)
|
|
327
328
|
else:
|
|
328
|
-
|
|
329
|
+
if reversal:
|
|
330
|
+
# Reversal is valid only for single glyph substitutions
|
|
331
|
+
# and VOLT ignores it otherwise.
|
|
332
|
+
sub = ast.SubstitutionReverseChainingSingleDefinition(
|
|
333
|
+
mapping, location=location
|
|
334
|
+
)
|
|
335
|
+
else:
|
|
336
|
+
sub = ast.SubstitutionSingleDefinition(mapping, location=location)
|
|
329
337
|
elif max_src == 1 and max_dest > 1:
|
|
330
338
|
sub = ast.SubstitutionMultipleDefinition(mapping, location=location)
|
|
331
339
|
elif max_src > 1 and max_dest == 1:
|