fonttools 4.55.8__cp38-cp38-win_amd64.whl → 4.57.0__cp38-cp38-win_amd64.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.
Potentially problematic release.
This version of fonttools might be problematic. Click here for more details.
- fontTools/__init__.py +1 -1
- fontTools/config/__init__.py +15 -0
- fontTools/cu2qu/cu2qu.cp38-win_amd64.pyd +0 -0
- fontTools/feaLib/ast.py +8 -3
- fontTools/feaLib/builder.py +32 -9
- fontTools/feaLib/error.py +1 -1
- fontTools/feaLib/lexer.cp38-win_amd64.pyd +0 -0
- fontTools/feaLib/parser.py +58 -0
- fontTools/misc/bezierTools.cp38-win_amd64.pyd +0 -0
- fontTools/misc/testTools.py +5 -1
- fontTools/otlLib/optimize/gpos.py +7 -1
- fontTools/pens/momentsPen.cp38-win_amd64.pyd +0 -0
- fontTools/qu2cu/qu2cu.cp38-win_amd64.pyd +0 -0
- fontTools/ttLib/__init__.py +4 -0
- fontTools/ttLib/__main__.py +47 -8
- fontTools/ttLib/tables/D__e_b_g.py +20 -2
- fontTools/ttLib/tables/_c_m_a_p.py +19 -6
- fontTools/ttLib/tables/_g_l_y_f.py +9 -4
- fontTools/ttLib/tables/_g_v_a_r.py +4 -2
- fontTools/ttLib/tables/otConverters.py +5 -2
- fontTools/ttLib/tables/otData.py +1 -1
- fontTools/ttLib/tables/otTables.py +26 -18
- fontTools/ttLib/tables/otTraverse.py +2 -1
- fontTools/ttLib/ttFont.py +9 -7
- fontTools/ttLib/ttGlyphSet.py +0 -10
- fontTools/ttx.py +13 -1
- fontTools/varLib/__init__.py +93 -101
- fontTools/varLib/hvar.py +113 -0
- fontTools/varLib/iup.cp38-win_amd64.pyd +0 -0
- fontTools/varLib/varStore.py +10 -38
- {fonttools-4.55.8.dist-info → fonttools-4.57.0.dist-info}/METADATA +23 -1
- {fonttools-4.55.8.dist-info → fonttools-4.57.0.dist-info}/RECORD +37 -36
- {fonttools-4.55.8.dist-info → fonttools-4.57.0.dist-info}/WHEEL +1 -1
- {fonttools-4.55.8.data → fonttools-4.57.0.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.55.8.dist-info → fonttools-4.57.0.dist-info}/LICENSE +0 -0
- {fonttools-4.55.8.dist-info → fonttools-4.57.0.dist-info}/entry_points.txt +0 -0
- {fonttools-4.55.8.dist-info → fonttools-4.57.0.dist-info}/top_level.txt +0 -0
|
@@ -79,7 +79,8 @@ def bfs_base_table(
|
|
|
79
79
|
"""Breadth-first search tree of BaseTables.
|
|
80
80
|
|
|
81
81
|
Args:
|
|
82
|
-
|
|
82
|
+
root
|
|
83
|
+
the root of the tree.
|
|
83
84
|
root_accessor (Optional[str]): attribute name for the root table, if any (mostly
|
|
84
85
|
useful for debugging).
|
|
85
86
|
skip_root (Optional[bool]): if True, the root itself is not visited, only its
|
fontTools/ttLib/ttFont.py
CHANGED
|
@@ -27,6 +27,7 @@ class TTFont(object):
|
|
|
27
27
|
they're actually accessed. This means that simple operations can be extremely fast.
|
|
28
28
|
|
|
29
29
|
Example usage:
|
|
30
|
+
|
|
30
31
|
.. code-block:: pycon
|
|
31
32
|
|
|
32
33
|
>>>
|
|
@@ -39,8 +40,10 @@ class TTFont(object):
|
|
|
39
40
|
>> tt['head'].unitsPerEm
|
|
40
41
|
2048
|
|
41
42
|
|
|
42
|
-
For details of the objects returned when accessing each table, see
|
|
43
|
+
For details of the objects returned when accessing each table, see the
|
|
44
|
+
:doc:`tables </ttLib/tables>` documentation.
|
|
43
45
|
To add a table to the font, use the :py:func:`newTable` function:
|
|
46
|
+
|
|
44
47
|
.. code-block:: pycon
|
|
45
48
|
|
|
46
49
|
>>>
|
|
@@ -50,7 +53,8 @@ class TTFont(object):
|
|
|
50
53
|
>> font["OS/2"] = os2
|
|
51
54
|
|
|
52
55
|
TrueType fonts can also be serialized to and from XML format (see also the
|
|
53
|
-
:
|
|
56
|
+
:doc:`ttx </ttx>` binary):
|
|
57
|
+
|
|
54
58
|
.. code-block:: pycon
|
|
55
59
|
|
|
56
60
|
>>
|
|
@@ -589,10 +593,8 @@ class TTFont(object):
|
|
|
589
593
|
# temporary cmap and by the real cmap in case we don't find a unicode
|
|
590
594
|
# cmap.
|
|
591
595
|
numGlyphs = int(self["maxp"].numGlyphs)
|
|
592
|
-
glyphOrder = [
|
|
596
|
+
glyphOrder = ["glyph%.5d" % i for i in range(numGlyphs)]
|
|
593
597
|
glyphOrder[0] = ".notdef"
|
|
594
|
-
for i in range(1, numGlyphs):
|
|
595
|
-
glyphOrder[i] = "glyph%.5d" % i
|
|
596
598
|
# Set the glyph order, so the cmap parser has something
|
|
597
599
|
# to work with (so we don't get called recursively).
|
|
598
600
|
self.glyphOrder = glyphOrder
|
|
@@ -602,7 +604,7 @@ class TTFont(object):
|
|
|
602
604
|
# this naming table will usually not cover all glyphs in the font.
|
|
603
605
|
# If the font has no Unicode cmap table, reversecmap will be empty.
|
|
604
606
|
if "cmap" in self:
|
|
605
|
-
reversecmap = self["cmap"].
|
|
607
|
+
reversecmap = self["cmap"].buildReversedMin()
|
|
606
608
|
else:
|
|
607
609
|
reversecmap = {}
|
|
608
610
|
useCount = {}
|
|
@@ -612,7 +614,7 @@ class TTFont(object):
|
|
|
612
614
|
# If a font maps both U+0041 LATIN CAPITAL LETTER A and
|
|
613
615
|
# U+0391 GREEK CAPITAL LETTER ALPHA to the same glyph,
|
|
614
616
|
# we prefer naming the glyph as "A".
|
|
615
|
-
glyphName = self._makeGlyphName(
|
|
617
|
+
glyphName = self._makeGlyphName(reversecmap[tempName])
|
|
616
618
|
numUses = useCount[glyphName] = useCount.get(glyphName, 0) + 1
|
|
617
619
|
if numUses > 1:
|
|
618
620
|
glyphName = "%s.alt%d" % (glyphName, numUses - 1)
|
fontTools/ttLib/ttGlyphSet.py
CHANGED
|
@@ -104,16 +104,6 @@ class _TTGlyphSetGlyf(_TTGlyphSet):
|
|
|
104
104
|
return _TTGlyphGlyf(self, glyphName, recalcBounds=self.recalcBounds)
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
class _TTGlyphSetGlyf(_TTGlyphSet):
|
|
108
|
-
def __init__(self, font, location, recalcBounds=True):
|
|
109
|
-
self.glyfTable = font["glyf"]
|
|
110
|
-
super().__init__(font, location, self.glyfTable, recalcBounds=recalcBounds)
|
|
111
|
-
self.gvarTable = font.get("gvar")
|
|
112
|
-
|
|
113
|
-
def __getitem__(self, glyphName):
|
|
114
|
-
return _TTGlyphGlyf(self, glyphName, recalcBounds=self.recalcBounds)
|
|
115
|
-
|
|
116
|
-
|
|
117
107
|
class _TTGlyphSetCFF(_TTGlyphSet):
|
|
118
108
|
def __init__(self, font, location):
|
|
119
109
|
tableTag = "CFF2" if "CFF2" in font else "CFF "
|
fontTools/ttx.py
CHANGED
|
@@ -101,9 +101,15 @@ Compile options
|
|
|
101
101
|
--with-zopfli
|
|
102
102
|
Use Zopfli instead of Zlib to compress WOFF. The Python
|
|
103
103
|
extension is available at https://pypi.python.org/pypi/zopfli
|
|
104
|
+
--optimize-font-speed
|
|
105
|
+
Enable optimizations that prioritize speed over file size.
|
|
106
|
+
This mainly affects how glyf t able and gvar / VARC tables are
|
|
107
|
+
compiled. The produced fonts will be larger, but rendering
|
|
108
|
+
performance will be improved with HarfBuzz and other text
|
|
109
|
+
layout engines.
|
|
104
110
|
"""
|
|
105
111
|
|
|
106
|
-
from fontTools.ttLib import TTFont, TTLibError
|
|
112
|
+
from fontTools.ttLib import OPTIMIZE_FONT_SPEED, TTFont, TTLibError
|
|
107
113
|
from fontTools.misc.macCreatorType import getMacCreatorAndType
|
|
108
114
|
from fontTools.unicode import setUnicodeData
|
|
109
115
|
from fontTools.misc.textTools import Tag, tostr
|
|
@@ -141,6 +147,7 @@ class Options(object):
|
|
|
141
147
|
recalcTimestamp = None
|
|
142
148
|
flavor = None
|
|
143
149
|
useZopfli = False
|
|
150
|
+
optimizeFontSpeed = False
|
|
144
151
|
|
|
145
152
|
def __init__(self, rawOptions, numFiles):
|
|
146
153
|
self.onlyTables = []
|
|
@@ -229,6 +236,8 @@ class Options(object):
|
|
|
229
236
|
self.flavor = value
|
|
230
237
|
elif option == "--with-zopfli":
|
|
231
238
|
self.useZopfli = True
|
|
239
|
+
elif option == "--optimize-font-speed":
|
|
240
|
+
self.optimizeFontSpeed = True
|
|
232
241
|
if self.verbose and self.quiet:
|
|
233
242
|
raise getopt.GetoptError("-q and -v options are mutually exclusive")
|
|
234
243
|
if self.verbose:
|
|
@@ -324,6 +333,8 @@ def ttCompile(input, output, options):
|
|
|
324
333
|
recalcBBoxes=options.recalcBBoxes,
|
|
325
334
|
recalcTimestamp=options.recalcTimestamp,
|
|
326
335
|
)
|
|
336
|
+
if options.optimizeFontSpeed:
|
|
337
|
+
ttf.cfg[OPTIMIZE_FONT_SPEED] = options.optimizeFontSpeed
|
|
327
338
|
ttf.importXML(input)
|
|
328
339
|
|
|
329
340
|
if options.recalcTimestamp is None and "head" in ttf and input is not sys.stdin:
|
|
@@ -386,6 +397,7 @@ def parseOptions(args):
|
|
|
386
397
|
"version",
|
|
387
398
|
"with-zopfli",
|
|
388
399
|
"newline=",
|
|
400
|
+
"optimize-font-speed",
|
|
389
401
|
],
|
|
390
402
|
)
|
|
391
403
|
|
fontTools/varLib/__init__.py
CHANGED
|
@@ -12,13 +12,13 @@ a Glyphs source, eg., using noto-source as an example:
|
|
|
12
12
|
|
|
13
13
|
.. code-block:: sh
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
$ fontmake -o ttf-interpolatable -g NotoSansArabic-MM.glyphs
|
|
16
16
|
|
|
17
17
|
Then you can make a variable-font this way:
|
|
18
18
|
|
|
19
19
|
.. code-block:: sh
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
$ fonttools varLib master_ufo/NotoSansArabic.designspace
|
|
22
22
|
|
|
23
23
|
API *will* change in near future.
|
|
24
24
|
"""
|
|
@@ -325,7 +325,6 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
|
|
|
325
325
|
|
|
326
326
|
for glyph in font.getGlyphOrder():
|
|
327
327
|
log.debug("building gvar for glyph '%s'", glyph)
|
|
328
|
-
isComposite = glyf[glyph].isComposite()
|
|
329
328
|
|
|
330
329
|
allData = [
|
|
331
330
|
m.glyf._getCoordinatesAndControls(glyph, m.hMetrics, m.vMetrics)
|
|
@@ -363,7 +362,7 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
|
|
|
363
362
|
endPts = control.endPts
|
|
364
363
|
|
|
365
364
|
for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])):
|
|
366
|
-
if all(v == 0 for v in delta.array)
|
|
365
|
+
if all(v == 0 for v in delta.array):
|
|
367
366
|
continue
|
|
368
367
|
var = TupleVariation(support, delta)
|
|
369
368
|
if optimize:
|
|
@@ -372,16 +371,6 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
|
|
|
372
371
|
)
|
|
373
372
|
|
|
374
373
|
if None in delta_opt:
|
|
375
|
-
"""In composite glyphs, there should be one 0 entry
|
|
376
|
-
to make sure the gvar entry is written to the font.
|
|
377
|
-
|
|
378
|
-
This is to work around an issue with macOS 10.14 and can be
|
|
379
|
-
removed once the behaviour of macOS is changed.
|
|
380
|
-
|
|
381
|
-
https://github.com/fonttools/fonttools/issues/1381
|
|
382
|
-
"""
|
|
383
|
-
if all(d is None for d in delta_opt):
|
|
384
|
-
delta_opt = [(0, 0)] + [None] * (len(delta_opt) - 1)
|
|
385
374
|
# Use "optimized" version only if smaller...
|
|
386
375
|
var_opt = TupleVariation(support, delta_opt)
|
|
387
376
|
|
|
@@ -490,7 +479,15 @@ def _merge_TTHinting(font, masterModel, master_ttfs):
|
|
|
490
479
|
|
|
491
480
|
_MetricsFields = namedtuple(
|
|
492
481
|
"_MetricsFields",
|
|
493
|
-
[
|
|
482
|
+
[
|
|
483
|
+
"tableTag",
|
|
484
|
+
"metricsTag",
|
|
485
|
+
"sb1",
|
|
486
|
+
"sb2",
|
|
487
|
+
"advMapping",
|
|
488
|
+
"vOrigMapping",
|
|
489
|
+
"phantomIndex",
|
|
490
|
+
],
|
|
494
491
|
)
|
|
495
492
|
|
|
496
493
|
HVAR_FIELDS = _MetricsFields(
|
|
@@ -500,6 +497,7 @@ HVAR_FIELDS = _MetricsFields(
|
|
|
500
497
|
sb2="RsbMap",
|
|
501
498
|
advMapping="AdvWidthMap",
|
|
502
499
|
vOrigMapping=None,
|
|
500
|
+
phantomIndex=0,
|
|
503
501
|
)
|
|
504
502
|
|
|
505
503
|
VVAR_FIELDS = _MetricsFields(
|
|
@@ -509,109 +507,43 @@ VVAR_FIELDS = _MetricsFields(
|
|
|
509
507
|
sb2="BsbMap",
|
|
510
508
|
advMapping="AdvHeightMap",
|
|
511
509
|
vOrigMapping="VOrgMap",
|
|
510
|
+
phantomIndex=1,
|
|
512
511
|
)
|
|
513
512
|
|
|
514
513
|
|
|
515
514
|
def _add_HVAR(font, masterModel, master_ttfs, axisTags):
|
|
516
|
-
|
|
515
|
+
getAdvanceMetrics = partial(
|
|
516
|
+
_get_advance_metrics, font, masterModel, master_ttfs, axisTags, HVAR_FIELDS
|
|
517
|
+
)
|
|
518
|
+
_add_VHVAR(font, axisTags, HVAR_FIELDS, getAdvanceMetrics)
|
|
517
519
|
|
|
518
520
|
|
|
519
521
|
def _add_VVAR(font, masterModel, master_ttfs, axisTags):
|
|
520
|
-
|
|
522
|
+
getAdvanceMetrics = partial(
|
|
523
|
+
_get_advance_metrics, font, masterModel, master_ttfs, axisTags, VVAR_FIELDS
|
|
524
|
+
)
|
|
525
|
+
_add_VHVAR(font, axisTags, VVAR_FIELDS, getAdvanceMetrics)
|
|
521
526
|
|
|
522
527
|
|
|
523
|
-
def _add_VHVAR(font,
|
|
528
|
+
def _add_VHVAR(font, axisTags, tableFields, getAdvanceMetrics):
|
|
524
529
|
tableTag = tableFields.tableTag
|
|
525
530
|
assert tableTag not in font
|
|
531
|
+
glyphOrder = font.getGlyphOrder()
|
|
526
532
|
log.info("Generating " + tableTag)
|
|
527
533
|
VHVAR = newTable(tableTag)
|
|
528
534
|
tableClass = getattr(ot, tableTag)
|
|
529
535
|
vhvar = VHVAR.table = tableClass()
|
|
530
536
|
vhvar.Version = 0x00010000
|
|
531
537
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
# Build list of source font advance widths for each glyph
|
|
535
|
-
metricsTag = tableFields.metricsTag
|
|
536
|
-
advMetricses = [m[metricsTag].metrics for m in master_ttfs]
|
|
537
|
-
|
|
538
|
-
# Build list of source font vertical origin coords for each glyph
|
|
539
|
-
if tableTag == "VVAR" and "VORG" in master_ttfs[0]:
|
|
540
|
-
vOrigMetricses = [m["VORG"].VOriginRecords for m in master_ttfs]
|
|
541
|
-
defaultYOrigs = [m["VORG"].defaultVertOriginY for m in master_ttfs]
|
|
542
|
-
vOrigMetricses = list(zip(vOrigMetricses, defaultYOrigs))
|
|
543
|
-
else:
|
|
544
|
-
vOrigMetricses = None
|
|
545
|
-
|
|
546
|
-
metricsStore, advanceMapping, vOrigMapping = _get_advance_metrics(
|
|
547
|
-
font,
|
|
548
|
-
masterModel,
|
|
549
|
-
master_ttfs,
|
|
550
|
-
axisTags,
|
|
551
|
-
glyphOrder,
|
|
552
|
-
advMetricses,
|
|
553
|
-
vOrigMetricses,
|
|
554
|
-
)
|
|
538
|
+
vhAdvanceDeltasAndSupports, vOrigDeltasAndSupports = getAdvanceMetrics()
|
|
555
539
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
setattr(vhvar, tableFields.advMapping, None)
|
|
540
|
+
if vOrigDeltasAndSupports:
|
|
541
|
+
singleModel = False
|
|
559
542
|
else:
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
setattr(vhvar, tableFields.vOrigMapping, vOrigMapping)
|
|
563
|
-
setattr(vhvar, tableFields.sb1, None)
|
|
564
|
-
setattr(vhvar, tableFields.sb2, None)
|
|
565
|
-
|
|
566
|
-
font[tableTag] = VHVAR
|
|
567
|
-
return
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
def _get_advance_metrics(
|
|
571
|
-
font,
|
|
572
|
-
masterModel,
|
|
573
|
-
master_ttfs,
|
|
574
|
-
axisTags,
|
|
575
|
-
glyphOrder,
|
|
576
|
-
advMetricses,
|
|
577
|
-
vOrigMetricses=None,
|
|
578
|
-
):
|
|
579
|
-
vhAdvanceDeltasAndSupports = {}
|
|
580
|
-
vOrigDeltasAndSupports = {}
|
|
581
|
-
# HACK: we treat width 65535 as a sentinel value to signal that a glyph
|
|
582
|
-
# from a non-default master should not participate in computing {H,V}VAR,
|
|
583
|
-
# as if it were missing. Allows to variate other glyph-related data independently
|
|
584
|
-
# from glyph metrics
|
|
585
|
-
sparse_advance = 0xFFFF
|
|
586
|
-
for glyph in glyphOrder:
|
|
587
|
-
vhAdvances = [
|
|
588
|
-
(
|
|
589
|
-
metrics[glyph][0]
|
|
590
|
-
if glyph in metrics and metrics[glyph][0] != sparse_advance
|
|
591
|
-
else None
|
|
592
|
-
)
|
|
593
|
-
for metrics in advMetricses
|
|
594
|
-
]
|
|
595
|
-
vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(
|
|
596
|
-
vhAdvances, round=round
|
|
543
|
+
singleModel = models.allEqual(
|
|
544
|
+
id(v[1]) for v in vhAdvanceDeltasAndSupports.values()
|
|
597
545
|
)
|
|
598
546
|
|
|
599
|
-
singleModel = models.allEqual(id(v[1]) for v in vhAdvanceDeltasAndSupports.values())
|
|
600
|
-
|
|
601
|
-
if vOrigMetricses:
|
|
602
|
-
singleModel = False
|
|
603
|
-
for glyph in glyphOrder:
|
|
604
|
-
# We need to supply a vOrigs tuple with non-None default values
|
|
605
|
-
# for each glyph. vOrigMetricses contains values only for those
|
|
606
|
-
# glyphs which have a non-default vOrig.
|
|
607
|
-
vOrigs = [
|
|
608
|
-
metrics[glyph] if glyph in metrics else defaultVOrig
|
|
609
|
-
for metrics, defaultVOrig in vOrigMetricses
|
|
610
|
-
]
|
|
611
|
-
vOrigDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(
|
|
612
|
-
vOrigs, round=round
|
|
613
|
-
)
|
|
614
|
-
|
|
615
547
|
directStore = None
|
|
616
548
|
if singleModel:
|
|
617
549
|
# Build direct mapping
|
|
@@ -632,7 +564,7 @@ def _get_advance_metrics(
|
|
|
632
564
|
storeBuilder.setSupports(supports)
|
|
633
565
|
advMapping[glyphName] = storeBuilder.storeDeltas(deltas, round=noRound)
|
|
634
566
|
|
|
635
|
-
if
|
|
567
|
+
if vOrigDeltasAndSupports:
|
|
636
568
|
vOrigMap = {}
|
|
637
569
|
for glyphName in glyphOrder:
|
|
638
570
|
deltas, supports = vOrigDeltasAndSupports[glyphName]
|
|
@@ -644,7 +576,7 @@ def _get_advance_metrics(
|
|
|
644
576
|
advMapping = [mapping2[advMapping[g]] for g in glyphOrder]
|
|
645
577
|
advanceMapping = builder.buildVarIdxMap(advMapping, glyphOrder)
|
|
646
578
|
|
|
647
|
-
if
|
|
579
|
+
if vOrigDeltasAndSupports:
|
|
648
580
|
vOrigMap = [mapping2[vOrigMap[g]] for g in glyphOrder]
|
|
649
581
|
|
|
650
582
|
useDirect = False
|
|
@@ -668,10 +600,70 @@ def _get_advance_metrics(
|
|
|
668
600
|
advanceMapping = None
|
|
669
601
|
else:
|
|
670
602
|
metricsStore = indirectStore
|
|
671
|
-
if
|
|
603
|
+
if vOrigDeltasAndSupports:
|
|
672
604
|
vOrigMapping = builder.buildVarIdxMap(vOrigMap, glyphOrder)
|
|
673
605
|
|
|
674
|
-
|
|
606
|
+
vhvar.VarStore = metricsStore
|
|
607
|
+
setattr(vhvar, tableFields.advMapping, advanceMapping)
|
|
608
|
+
if vOrigMapping is not None:
|
|
609
|
+
setattr(vhvar, tableFields.vOrigMapping, vOrigMapping)
|
|
610
|
+
setattr(vhvar, tableFields.sb1, None)
|
|
611
|
+
setattr(vhvar, tableFields.sb2, None)
|
|
612
|
+
|
|
613
|
+
font[tableTag] = VHVAR
|
|
614
|
+
return
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _get_advance_metrics(font, masterModel, master_ttfs, axisTags, tableFields):
|
|
618
|
+
tableTag = tableFields.tableTag
|
|
619
|
+
glyphOrder = font.getGlyphOrder()
|
|
620
|
+
|
|
621
|
+
# Build list of source font advance widths for each glyph
|
|
622
|
+
metricsTag = tableFields.metricsTag
|
|
623
|
+
advMetricses = [m[metricsTag].metrics for m in master_ttfs]
|
|
624
|
+
|
|
625
|
+
# Build list of source font vertical origin coords for each glyph
|
|
626
|
+
if tableTag == "VVAR" and "VORG" in master_ttfs[0]:
|
|
627
|
+
vOrigMetricses = [m["VORG"].VOriginRecords for m in master_ttfs]
|
|
628
|
+
defaultYOrigs = [m["VORG"].defaultVertOriginY for m in master_ttfs]
|
|
629
|
+
vOrigMetricses = list(zip(vOrigMetricses, defaultYOrigs))
|
|
630
|
+
else:
|
|
631
|
+
vOrigMetricses = None
|
|
632
|
+
|
|
633
|
+
vhAdvanceDeltasAndSupports = {}
|
|
634
|
+
vOrigDeltasAndSupports = {}
|
|
635
|
+
# HACK: we treat width 65535 as a sentinel value to signal that a glyph
|
|
636
|
+
# from a non-default master should not participate in computing {H,V}VAR,
|
|
637
|
+
# as if it were missing. Allows to variate other glyph-related data independently
|
|
638
|
+
# from glyph metrics
|
|
639
|
+
sparse_advance = 0xFFFF
|
|
640
|
+
for glyph in glyphOrder:
|
|
641
|
+
vhAdvances = [
|
|
642
|
+
(
|
|
643
|
+
metrics[glyph][0]
|
|
644
|
+
if glyph in metrics and metrics[glyph][0] != sparse_advance
|
|
645
|
+
else None
|
|
646
|
+
)
|
|
647
|
+
for metrics in advMetricses
|
|
648
|
+
]
|
|
649
|
+
vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(
|
|
650
|
+
vhAdvances, round=round
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
if vOrigMetricses:
|
|
654
|
+
for glyph in glyphOrder:
|
|
655
|
+
# We need to supply a vOrigs tuple with non-None default values
|
|
656
|
+
# for each glyph. vOrigMetricses contains values only for those
|
|
657
|
+
# glyphs which have a non-default vOrig.
|
|
658
|
+
vOrigs = [
|
|
659
|
+
metrics[glyph] if glyph in metrics else defaultVOrig
|
|
660
|
+
for metrics, defaultVOrig in vOrigMetricses
|
|
661
|
+
]
|
|
662
|
+
vOrigDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(
|
|
663
|
+
vOrigs, round=round
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
return vhAdvanceDeltasAndSupports, vOrigDeltasAndSupports
|
|
675
667
|
|
|
676
668
|
|
|
677
669
|
def _add_MVAR(font, masterModel, master_ttfs, axisTags):
|
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 remoding 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, HVAR_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())
|
|
Binary file
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fonttools
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.57.0
|
|
4
4
|
Summary: Tools to manipulate font files
|
|
5
5
|
Home-page: http://github.com/fonttools/fonttools
|
|
6
6
|
Author: Just van Rossum
|
|
@@ -378,6 +378,28 @@ Have fun!
|
|
|
378
378
|
Changelog
|
|
379
379
|
~~~~~~~~~
|
|
380
380
|
|
|
381
|
+
4.57.0 (released 2025-04-03)
|
|
382
|
+
----------------------------
|
|
383
|
+
|
|
384
|
+
- [ttLib.__main__] Add `--no-recalc-timestamp` flag (#3771)
|
|
385
|
+
- [ttLib.__main__] Add `-b` (recalcBBoxes=False) flag (#3772)
|
|
386
|
+
- [cmap] Speed up glyphOrder loading from cmap (#3774)
|
|
387
|
+
- [ttLib.__main__] Improvements around the `-t` flag (#3776)
|
|
388
|
+
- [Debg] Fix parsing from XML; add roundtrip tests (#3781)
|
|
389
|
+
- [fealib] Support \*Base.MinMax tables (#3783, #3786)
|
|
390
|
+
- [config] Add OPTIMIZE_FONT_SPEED (#3784)
|
|
391
|
+
- [varLib.hvar] New module to add HVAR table to the font (#3780)
|
|
392
|
+
- [otlLib.optimize] Fix crash when the provided TTF does not contain a `GPOS` (#3794)
|
|
393
|
+
|
|
394
|
+
4.56.0 (released 2025-02-07)
|
|
395
|
+
----------------------------
|
|
396
|
+
|
|
397
|
+
- [varStore] Sort the input todo list with the same sorting key used for the opimizer's output (#3767).
|
|
398
|
+
- [otData] Fix DeviceTable's ``DeltaValue`` repeat value which caused a crash after importing from XML and then compiling a GPOS containing Device tables (#3758).
|
|
399
|
+
- [feaLib] Make ``FeatureLibError`` pickleable, so client can e.g. use feaLib to can compile features in parallel with multiprocessing (#3762).
|
|
400
|
+
- [varLib/gvar] Removed workaround for old, long-fixed macOS bug about composite glyphs with all zero deltas (#1381, #1788).
|
|
401
|
+
- [Docs] Updated ttLib documentation, beefed up TTFont and TTGlyphSet explanations (#3720).
|
|
402
|
+
|
|
381
403
|
4.55.8 (released 2025-01-29)
|
|
382
404
|
----------------------------
|
|
383
405
|
|