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.
Files changed (140) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/annotations.py +30 -0
  3. fontTools/cffLib/CFF2ToCFF.py +65 -10
  4. fontTools/cffLib/__init__.py +61 -26
  5. fontTools/cffLib/specializer.py +4 -1
  6. fontTools/cffLib/transforms.py +11 -6
  7. fontTools/config/__init__.py +15 -0
  8. fontTools/cu2qu/cu2qu.c +6567 -5579
  9. fontTools/cu2qu/cu2qu.cpython-313-aarch64-linux-musl.so +0 -0
  10. fontTools/cu2qu/cu2qu.py +36 -4
  11. fontTools/cu2qu/ufo.py +14 -0
  12. fontTools/designspaceLib/__init__.py +8 -3
  13. fontTools/designspaceLib/statNames.py +14 -7
  14. fontTools/feaLib/ast.py +24 -15
  15. fontTools/feaLib/builder.py +139 -66
  16. fontTools/feaLib/error.py +1 -1
  17. fontTools/feaLib/lexer.c +7038 -7995
  18. fontTools/feaLib/lexer.cpython-313-aarch64-linux-musl.so +0 -0
  19. fontTools/feaLib/parser.py +75 -40
  20. fontTools/feaLib/variableScalar.py +6 -1
  21. fontTools/fontBuilder.py +50 -44
  22. fontTools/merge/__init__.py +1 -1
  23. fontTools/merge/cmap.py +33 -1
  24. fontTools/merge/tables.py +12 -1
  25. fontTools/misc/bezierTools.c +14913 -17013
  26. fontTools/misc/bezierTools.cpython-313-aarch64-linux-musl.so +0 -0
  27. fontTools/misc/bezierTools.py +4 -1
  28. fontTools/misc/configTools.py +3 -1
  29. fontTools/misc/enumTools.py +23 -0
  30. fontTools/misc/etree.py +4 -27
  31. fontTools/misc/filesystem/__init__.py +68 -0
  32. fontTools/misc/filesystem/_base.py +134 -0
  33. fontTools/misc/filesystem/_copy.py +45 -0
  34. fontTools/misc/filesystem/_errors.py +54 -0
  35. fontTools/misc/filesystem/_info.py +75 -0
  36. fontTools/misc/filesystem/_osfs.py +164 -0
  37. fontTools/misc/filesystem/_path.py +67 -0
  38. fontTools/misc/filesystem/_subfs.py +92 -0
  39. fontTools/misc/filesystem/_tempfs.py +34 -0
  40. fontTools/misc/filesystem/_tools.py +34 -0
  41. fontTools/misc/filesystem/_walk.py +55 -0
  42. fontTools/misc/filesystem/_zipfs.py +204 -0
  43. fontTools/misc/fixedTools.py +1 -1
  44. fontTools/misc/loggingTools.py +1 -1
  45. fontTools/misc/psCharStrings.py +17 -2
  46. fontTools/misc/sstruct.py +2 -6
  47. fontTools/misc/symfont.py +6 -8
  48. fontTools/misc/testTools.py +5 -1
  49. fontTools/misc/textTools.py +4 -2
  50. fontTools/misc/visitor.py +32 -16
  51. fontTools/misc/xmlWriter.py +44 -8
  52. fontTools/mtiLib/__init__.py +1 -3
  53. fontTools/otlLib/builder.py +402 -155
  54. fontTools/otlLib/optimize/gpos.py +49 -63
  55. fontTools/pens/filterPen.py +218 -26
  56. fontTools/pens/momentsPen.c +5514 -5584
  57. fontTools/pens/momentsPen.cpython-313-aarch64-linux-musl.so +0 -0
  58. fontTools/pens/pointPen.py +61 -18
  59. fontTools/pens/roundingPen.py +2 -2
  60. fontTools/pens/t2CharStringPen.py +31 -11
  61. fontTools/qu2cu/qu2cu.c +6581 -6168
  62. fontTools/qu2cu/qu2cu.cpython-313-aarch64-linux-musl.so +0 -0
  63. fontTools/subset/__init__.py +283 -25
  64. fontTools/subset/svg.py +2 -3
  65. fontTools/ttLib/__init__.py +4 -0
  66. fontTools/ttLib/__main__.py +47 -8
  67. fontTools/ttLib/removeOverlaps.py +7 -5
  68. fontTools/ttLib/reorderGlyphs.py +8 -7
  69. fontTools/ttLib/sfnt.py +11 -9
  70. fontTools/ttLib/tables/D__e_b_g.py +20 -2
  71. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  72. fontTools/ttLib/tables/S__i_l_f.py +2 -2
  73. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  74. fontTools/ttLib/tables/T_S_I__1.py +2 -5
  75. fontTools/ttLib/tables/T_S_I__5.py +18 -7
  76. fontTools/ttLib/tables/__init__.py +1 -0
  77. fontTools/ttLib/tables/_a_v_a_r.py +12 -3
  78. fontTools/ttLib/tables/_c_m_a_p.py +20 -7
  79. fontTools/ttLib/tables/_c_v_t.py +3 -2
  80. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  81. fontTools/ttLib/tables/_g_l_y_f.py +45 -21
  82. fontTools/ttLib/tables/_g_v_a_r.py +67 -19
  83. fontTools/ttLib/tables/_h_d_m_x.py +4 -4
  84. fontTools/ttLib/tables/_h_m_t_x.py +7 -3
  85. fontTools/ttLib/tables/_l_o_c_a.py +2 -2
  86. fontTools/ttLib/tables/_n_a_m_e.py +11 -6
  87. fontTools/ttLib/tables/_p_o_s_t.py +9 -7
  88. fontTools/ttLib/tables/otBase.py +5 -12
  89. fontTools/ttLib/tables/otConverters.py +5 -2
  90. fontTools/ttLib/tables/otData.py +1 -1
  91. fontTools/ttLib/tables/otTables.py +33 -30
  92. fontTools/ttLib/tables/otTraverse.py +2 -1
  93. fontTools/ttLib/tables/sbixStrike.py +3 -3
  94. fontTools/ttLib/ttFont.py +666 -120
  95. fontTools/ttLib/ttGlyphSet.py +0 -10
  96. fontTools/ttLib/woff2.py +10 -13
  97. fontTools/ttx.py +13 -1
  98. fontTools/ufoLib/__init__.py +300 -202
  99. fontTools/ufoLib/converters.py +103 -30
  100. fontTools/ufoLib/errors.py +8 -0
  101. fontTools/ufoLib/etree.py +1 -1
  102. fontTools/ufoLib/filenames.py +171 -106
  103. fontTools/ufoLib/glifLib.py +303 -205
  104. fontTools/ufoLib/kerning.py +98 -48
  105. fontTools/ufoLib/utils.py +46 -15
  106. fontTools/ufoLib/validators.py +121 -99
  107. fontTools/unicodedata/Blocks.py +35 -20
  108. fontTools/unicodedata/Mirrored.py +446 -0
  109. fontTools/unicodedata/ScriptExtensions.py +63 -37
  110. fontTools/unicodedata/Scripts.py +173 -152
  111. fontTools/unicodedata/__init__.py +10 -2
  112. fontTools/varLib/__init__.py +198 -109
  113. fontTools/varLib/avar/__init__.py +0 -0
  114. fontTools/varLib/avar/__main__.py +72 -0
  115. fontTools/varLib/avar/build.py +79 -0
  116. fontTools/varLib/avar/map.py +108 -0
  117. fontTools/varLib/avar/plan.py +1004 -0
  118. fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
  119. fontTools/varLib/avarPlanner.py +3 -999
  120. fontTools/varLib/featureVars.py +21 -7
  121. fontTools/varLib/hvar.py +113 -0
  122. fontTools/varLib/instancer/__init__.py +180 -65
  123. fontTools/varLib/interpolatableHelpers.py +3 -0
  124. fontTools/varLib/iup.c +7564 -6903
  125. fontTools/varLib/iup.cpython-313-aarch64-linux-musl.so +0 -0
  126. fontTools/varLib/models.py +17 -2
  127. fontTools/varLib/mutator.py +11 -0
  128. fontTools/varLib/varStore.py +10 -38
  129. fontTools/voltLib/__main__.py +206 -0
  130. fontTools/voltLib/ast.py +4 -0
  131. fontTools/voltLib/parser.py +16 -8
  132. fontTools/voltLib/voltToFea.py +347 -166
  133. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/METADATA +269 -1410
  134. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/RECORD +318 -294
  135. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/WHEEL +1 -1
  136. fonttools-4.61.1.dist-info/licenses/LICENSE.external +388 -0
  137. {fonttools-4.55.4.data → fonttools-4.61.1.data}/data/share/man/man1/ttx.1 +0 -0
  138. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/entry_points.txt +0 -0
  139. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info/licenses}/LICENSE +0 -0
  140. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/top_level.txt +0 -0
@@ -15,8 +15,7 @@ except ImportError: # pragma: no cover
15
15
  # fall back to built-in unicodedata (possibly outdated)
16
16
  from unicodedata import *
17
17
 
18
- from . import Blocks, Scripts, ScriptExtensions, OTTags
19
-
18
+ from . import Blocks, Mirrored, Scripts, ScriptExtensions, OTTags
20
19
 
21
20
  __all__ = [
22
21
  # names from built-in unicodedata module
@@ -46,6 +45,11 @@ __all__ = [
46
45
  ]
47
46
 
48
47
 
48
+ def mirrored(code):
49
+ """If code (unicode codepoint) has a mirrored version returns it, otherwise None."""
50
+ return Mirrored.MIRRORED.get(code)
51
+
52
+
49
53
  def script(char):
50
54
  """Return the four-letter script code assigned to the Unicode character
51
55
  'char' as string.
@@ -193,6 +197,10 @@ RTL_SCRIPTS = {
193
197
  "Yezi", # Yezidi
194
198
  # Unicode-14.0 additions
195
199
  "Ougr", # Old Uyghur
200
+ # Unicode-16.0 additions
201
+ "Gara", # Garay
202
+ # Unicode-17.0 additions
203
+ "Sidt", # Sidetic
196
204
  }
197
205
 
198
206
 
@@ -12,13 +12,13 @@ a Glyphs source, eg., using noto-source as an example:
12
12
 
13
13
  .. code-block:: sh
14
14
 
15
- $ fontmake -o ttf-interpolatable -g NotoSansArabic-MM.glyphs
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
- $ fonttools varLib master_ufo/NotoSansArabic.designspace
21
+ $ fonttools varLib master_ufo/NotoSansArabic.designspace
22
22
 
23
23
  API *will* change in near future.
24
24
  """
@@ -30,7 +30,11 @@ from fontTools.misc.fixedTools import floatToFixed as fl2fi
30
30
  from fontTools.misc.textTools import Tag, tostr
31
31
  from fontTools.ttLib import TTFont, newTable
32
32
  from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance
33
- from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates, dropImpliedOnCurvePoints
33
+ from fontTools.ttLib.tables._g_l_y_f import (
34
+ GlyphCoordinates,
35
+ dropImpliedOnCurvePoints,
36
+ USE_MY_METRICS,
37
+ )
34
38
  from fontTools.ttLib.tables.ttProgram import Program
35
39
  from fontTools.ttLib.tables.TupleVariation import TupleVariation
36
40
  from fontTools.ttLib.tables import otTables as ot
@@ -109,6 +113,8 @@ def _add_fvar(font, axes, instances: List[InstanceDescriptor]):
109
113
  axis.flags = int(a.hidden)
110
114
  fvar.axes.append(axis)
111
115
 
116
+ default_coordinates = {axis.axisTag: axis.defaultValue for axis in fvar.axes}
117
+
112
118
  for instance in instances:
113
119
  # Filter out discrete axis locations
114
120
  coordinates = {
@@ -130,16 +136,26 @@ def _add_fvar(font, axes, instances: List[InstanceDescriptor]):
130
136
  psname = instance.postScriptFontName
131
137
 
132
138
  inst = NamedInstance()
133
- inst.subfamilyNameID = nameTable.addMultilingualName(
134
- localisedStyleName, mac=macNames
139
+ inst.coordinates = {
140
+ axes[k].tag: axes[k].map_backward(v) for k, v in coordinates.items()
141
+ }
142
+
143
+ subfamilyNameID = nameTable.findMultilingualName(
144
+ localisedStyleName, windows=True, mac=macNames
135
145
  )
146
+ if subfamilyNameID in {2, 17} and inst.coordinates == default_coordinates:
147
+ # Instances can only reuse an existing name ID 2 or 17 if they are at the
148
+ # default location across all axes, see:
149
+ # https://github.com/fonttools/fonttools/issues/3825.
150
+ inst.subfamilyNameID = subfamilyNameID
151
+ else:
152
+ inst.subfamilyNameID = nameTable.addMultilingualName(
153
+ localisedStyleName, windows=True, mac=macNames, minNameID=256
154
+ )
155
+
136
156
  if psname is not None:
137
157
  psname = tostr(psname)
138
158
  inst.postscriptNameID = nameTable.addName(psname, platforms=platforms)
139
- inst.coordinates = {
140
- axes[k].tag: axes[k].map_backward(v) for k, v in coordinates.items()
141
- }
142
- # inst.coordinates = {axes[k].tag:v for k,v in coordinates.items()}
143
159
  fvar.instances.append(inst)
144
160
 
145
161
  assert "fvar" not in font
@@ -325,7 +341,6 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
325
341
 
326
342
  for glyph in font.getGlyphOrder():
327
343
  log.debug("building gvar for glyph '%s'", glyph)
328
- isComposite = glyf[glyph].isComposite()
329
344
 
330
345
  allData = [
331
346
  m.glyf._getCoordinatesAndControls(glyph, m.hMetrics, m.vMetrics)
@@ -363,7 +378,7 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
363
378
  endPts = control.endPts
364
379
 
365
380
  for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])):
366
- if all(v == 0 for v in delta.array) and not isComposite:
381
+ if all(v == 0 for v in delta.array):
367
382
  continue
368
383
  var = TupleVariation(support, delta)
369
384
  if optimize:
@@ -372,16 +387,6 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
372
387
  )
373
388
 
374
389
  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
390
  # Use "optimized" version only if smaller...
386
391
  var_opt = TupleVariation(support, delta_opt)
387
392
 
@@ -488,9 +493,88 @@ def _merge_TTHinting(font, masterModel, master_ttfs):
488
493
  cvar.variations = variations
489
494
 
490
495
 
496
+ def _has_inconsistent_use_my_metrics_flag(
497
+ master_glyf, glyph_name, flagged_components, expected_num_components
498
+ ) -> bool:
499
+ master_glyph = master_glyf.get(glyph_name)
500
+ # 'sparse' glyph master doesn't contribute. Besides when components don't match
501
+ # the VF build is going to fail anyway, so be lenient here.
502
+ if (
503
+ master_glyph is not None
504
+ and master_glyph.isComposite()
505
+ and len(master_glyph.components) == expected_num_components
506
+ ):
507
+ for i, base_glyph in flagged_components:
508
+ comp = master_glyph.components[i]
509
+ if comp.glyphName != base_glyph:
510
+ break
511
+ if not (comp.flags & USE_MY_METRICS):
512
+ return True
513
+ return False
514
+
515
+
516
+ def _unset_inconsistent_use_my_metrics_flags(vf, master_fonts):
517
+ """Clear USE_MY_METRICS on composite components if inconsistent across masters.
518
+
519
+ If a composite glyph's component has USE_MY_METRICS set differently among
520
+ the masters, the flag is removed from the variable font's glyf table so that
521
+ advance widths are not determined by that single component's phantom points.
522
+ """
523
+ glyf = vf["glyf"]
524
+ master_glyfs = [m["glyf"] for m in master_fonts if "glyf" in m]
525
+ if not master_glyfs:
526
+ # Should not happen: at least the base master (as copied into vf) has glyf
527
+ return
528
+
529
+ for glyph_name in glyf.keys():
530
+ glyph = glyf[glyph_name]
531
+ if not glyph.isComposite():
532
+ continue
533
+
534
+ # collect indices of component(s) that carry the USE_MY_METRICS flag.
535
+ # This is supposed to be 1 component per composite, but you never know.
536
+ flagged_components = [
537
+ (i, comp.glyphName)
538
+ for i, comp in enumerate(glyph.components)
539
+ if (comp.flags & USE_MY_METRICS)
540
+ ]
541
+ if not flagged_components:
542
+ # Nothing to fix
543
+ continue
544
+
545
+ # Verify that for all master glyf tables that contribute this glyph, the
546
+ # corresponding component (same glyphName and index) also carries USE_MY_METRICS
547
+ # and unset the flag if not.
548
+ expected_num_components = len(glyph.components)
549
+ if any(
550
+ _has_inconsistent_use_my_metrics_flag(
551
+ master_glyf, glyph_name, flagged_components, expected_num_components
552
+ )
553
+ for master_glyf in master_glyfs
554
+ ):
555
+ comp_names = [name for _, name in flagged_components]
556
+ log.info(
557
+ "Composite glyph '%s' has inconsistent USE_MY_METRICS flags across "
558
+ "masters; clearing the flag on component%s %s",
559
+ glyph_name,
560
+ "s" if len(comp_names) > 1 else "",
561
+ comp_names if len(comp_names) > 1 else comp_names[0],
562
+ )
563
+ for i, _ in flagged_components:
564
+ glyph.components[i].flags &= ~USE_MY_METRICS
565
+
566
+
491
567
  _MetricsFields = namedtuple(
492
568
  "_MetricsFields",
493
- ["tableTag", "metricsTag", "sb1", "sb2", "advMapping", "vOrigMapping"],
569
+ [
570
+ "tableTag",
571
+ "metricsTag",
572
+ "sb1",
573
+ "sb2",
574
+ "advMapping",
575
+ "vOrigMapping",
576
+ "phantomIndex",
577
+ ],
494
578
  )
495
579
 
496
580
  HVAR_FIELDS = _MetricsFields(
@@ -500,6 +584,7 @@ HVAR_FIELDS = _MetricsFields(
500
584
  sb2="RsbMap",
501
585
  advMapping="AdvWidthMap",
502
586
  vOrigMapping=None,
587
+ phantomIndex=0,
503
588
  )
504
589
 
505
590
  VVAR_FIELDS = _MetricsFields(
@@ -509,109 +594,43 @@ VVAR_FIELDS = _MetricsFields(
509
594
  sb2="BsbMap",
510
595
  advMapping="AdvHeightMap",
511
596
  vOrigMapping="VOrgMap",
597
+ phantomIndex=1,
512
598
  )
513
599
 
514
600
 
515
601
  def _add_HVAR(font, masterModel, master_ttfs, axisTags):
516
- _add_VHVAR(font, masterModel, master_ttfs, axisTags, HVAR_FIELDS)
602
+ getAdvanceMetrics = partial(
603
+ _get_advance_metrics, font, masterModel, master_ttfs, axisTags, HVAR_FIELDS
604
+ )
605
+ _add_VHVAR(font, axisTags, HVAR_FIELDS, getAdvanceMetrics)
517
606
 
518
607
 
519
608
  def _add_VVAR(font, masterModel, master_ttfs, axisTags):
520
- _add_VHVAR(font, masterModel, master_ttfs, axisTags, VVAR_FIELDS)
609
+ getAdvanceMetrics = partial(
610
+ _get_advance_metrics, font, masterModel, master_ttfs, axisTags, VVAR_FIELDS
611
+ )
612
+ _add_VHVAR(font, axisTags, VVAR_FIELDS, getAdvanceMetrics)
521
613
 
522
614
 
523
- def _add_VHVAR(font, masterModel, master_ttfs, axisTags, tableFields):
615
+ def _add_VHVAR(font, axisTags, tableFields, getAdvanceMetrics):
524
616
  tableTag = tableFields.tableTag
525
617
  assert tableTag not in font
618
+ glyphOrder = font.getGlyphOrder()
526
619
  log.info("Generating " + tableTag)
527
620
  VHVAR = newTable(tableTag)
528
621
  tableClass = getattr(ot, tableTag)
529
622
  vhvar = VHVAR.table = tableClass()
530
623
  vhvar.Version = 0x00010000
531
624
 
532
- glyphOrder = font.getGlyphOrder()
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
- )
625
+ vhAdvanceDeltasAndSupports, vOrigDeltasAndSupports = getAdvanceMetrics()
555
626
 
556
- vhvar.VarStore = metricsStore
557
- if advanceMapping is None:
558
- setattr(vhvar, tableFields.advMapping, None)
627
+ if vOrigDeltasAndSupports:
628
+ singleModel = False
559
629
  else:
560
- setattr(vhvar, tableFields.advMapping, advanceMapping)
561
- if vOrigMapping is not None:
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
630
+ singleModel = models.allEqual(
631
+ id(v[1]) for v in vhAdvanceDeltasAndSupports.values()
597
632
  )
598
633
 
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
634
  directStore = None
616
635
  if singleModel:
617
636
  # Build direct mapping
@@ -623,6 +642,8 @@ def _get_advance_metrics(
623
642
  varData.addItem(vhAdvanceDeltasAndSupports[glyphName][0], round=noRound)
624
643
  varData.optimize()
625
644
  directStore = builder.buildVarStore(varTupleList, [varData])
645
+ # remove unused regions from VarRegionList
646
+ directStore.prune_regions()
626
647
 
627
648
  # Build optimized indirect mapping
628
649
  storeBuilder = varStore.OnlineVarStoreBuilder(axisTags)
@@ -632,7 +653,7 @@ def _get_advance_metrics(
632
653
  storeBuilder.setSupports(supports)
633
654
  advMapping[glyphName] = storeBuilder.storeDeltas(deltas, round=noRound)
634
655
 
635
- if vOrigMetricses:
656
+ if vOrigDeltasAndSupports:
636
657
  vOrigMap = {}
637
658
  for glyphName in glyphOrder:
638
659
  deltas, supports = vOrigDeltasAndSupports[glyphName]
@@ -644,7 +665,7 @@ def _get_advance_metrics(
644
665
  advMapping = [mapping2[advMapping[g]] for g in glyphOrder]
645
666
  advanceMapping = builder.buildVarIdxMap(advMapping, glyphOrder)
646
667
 
647
- if vOrigMetricses:
668
+ if vOrigDeltasAndSupports:
648
669
  vOrigMap = [mapping2[vOrigMap[g]] for g in glyphOrder]
649
670
 
650
671
  useDirect = False
@@ -668,10 +689,70 @@ def _get_advance_metrics(
668
689
  advanceMapping = None
669
690
  else:
670
691
  metricsStore = indirectStore
671
- if vOrigMetricses:
692
+ if vOrigDeltasAndSupports:
672
693
  vOrigMapping = builder.buildVarIdxMap(vOrigMap, glyphOrder)
673
694
 
674
- return metricsStore, advanceMapping, vOrigMapping
695
+ vhvar.VarStore = metricsStore
696
+ setattr(vhvar, tableFields.advMapping, advanceMapping)
697
+ if vOrigMapping is not None:
698
+ setattr(vhvar, tableFields.vOrigMapping, vOrigMapping)
699
+ setattr(vhvar, tableFields.sb1, None)
700
+ setattr(vhvar, tableFields.sb2, None)
701
+
702
+ font[tableTag] = VHVAR
703
+ return
704
+
705
+
706
+ def _get_advance_metrics(font, masterModel, master_ttfs, axisTags, tableFields):
707
+ tableTag = tableFields.tableTag
708
+ glyphOrder = font.getGlyphOrder()
709
+
710
+ # Build list of source font advance widths for each glyph
711
+ metricsTag = tableFields.metricsTag
712
+ advMetricses = [m[metricsTag].metrics for m in master_ttfs]
713
+
714
+ # Build list of source font vertical origin coords for each glyph
715
+ if tableTag == "VVAR" and "VORG" in master_ttfs[0]:
716
+ vOrigMetricses = [m["VORG"].VOriginRecords for m in master_ttfs]
717
+ defaultYOrigs = [m["VORG"].defaultVertOriginY for m in master_ttfs]
718
+ vOrigMetricses = list(zip(vOrigMetricses, defaultYOrigs))
719
+ else:
720
+ vOrigMetricses = None
721
+
722
+ vhAdvanceDeltasAndSupports = {}
723
+ vOrigDeltasAndSupports = {}
724
+ # HACK: we treat width 65535 as a sentinel value to signal that a glyph
725
+ # from a non-default master should not participate in computing {H,V}VAR,
726
+ # as if it were missing. Allows to variate other glyph-related data independently
727
+ # from glyph metrics
728
+ sparse_advance = 0xFFFF
729
+ for glyph in glyphOrder:
730
+ vhAdvances = [
731
+ (
732
+ metrics[glyph][0]
733
+ if glyph in metrics and metrics[glyph][0] != sparse_advance
734
+ else None
735
+ )
736
+ for metrics in advMetricses
737
+ ]
738
+ vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(
739
+ vhAdvances, round=round
740
+ )
741
+
742
+ if vOrigMetricses:
743
+ for glyph in glyphOrder:
744
+ # We need to supply a vOrigs tuple with non-None default values
745
+ # for each glyph. vOrigMetricses contains values only for those
746
+ # glyphs which have a non-default vOrig.
747
+ vOrigs = [
748
+ metrics[glyph] if glyph in metrics else defaultVOrig
749
+ for metrics, defaultVOrig in vOrigMetricses
750
+ ]
751
+ vOrigDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(
752
+ vOrigs, round=round
753
+ )
754
+
755
+ return vhAdvanceDeltasAndSupports, vOrigDeltasAndSupports
675
756
 
676
757
 
677
758
  def _add_MVAR(font, masterModel, master_ttfs, axisTags):
@@ -1199,6 +1280,10 @@ def build(
1199
1280
  if "DSIG" in vf:
1200
1281
  del vf["DSIG"]
1201
1282
 
1283
+ # Clear USE_MY_METRICS composite flags if set inconsistently across masters.
1284
+ if "glyf" in vf:
1285
+ _unset_inconsistent_use_my_metrics_flags(vf, master_fonts)
1286
+
1202
1287
  # TODO append masters as named-instances as well; needs .designspace change.
1203
1288
  fvar = _add_fvar(vf, ds.axes, ds.instances)
1204
1289
  if "STAT" not in exclude:
@@ -1477,7 +1562,11 @@ def main(args=None):
1477
1562
  vf_name_to_output_path[vfs_to_build[0].name] = options.outfile
1478
1563
  else:
1479
1564
  for vf in vfs_to_build:
1480
- filename = vf.filename if vf.filename is not None else vf.name + ".{ext}"
1565
+ if vf.filename is not None:
1566
+ # Only use basename to prevent path traversal attacks
1567
+ filename = os.path.basename(vf.filename)
1568
+ else:
1569
+ filename = vf.name + ".{ext}"
1481
1570
  vf_name_to_output_path[vf.name] = os.path.join(output_dir, filename)
1482
1571
 
1483
1572
  finder = MasterFinder(options.master_finder)
File without changes
@@ -0,0 +1,72 @@
1
+ import logging
2
+
3
+ log = logging.getLogger("fontTools.varLib.avar")
4
+
5
+
6
+ def main(args=None):
7
+ from fontTools.ttLib import TTFont
8
+ from fontTools.misc.cliTools import makeOutputFileName
9
+ from fontTools import configLogger
10
+ import argparse
11
+ import sys
12
+
13
+ print(
14
+ "WARNING: This script is deprecated. Use `fonttools varLib.avar.build` "
15
+ "or `fonttools varLib.avar.unbuild` instead.\n",
16
+ file=sys.stderr,
17
+ )
18
+
19
+ if args is None:
20
+ args = sys.argv[1:]
21
+
22
+ parser = argparse.ArgumentParser(
23
+ "fonttools varLib.avar",
24
+ description="Add `avar` table from designspace file to variable font.",
25
+ )
26
+ parser.add_argument("font", metavar="varfont.ttf", help="Variable-font file.")
27
+ parser.add_argument(
28
+ "designspace",
29
+ metavar="family.designspace",
30
+ help="Designspace file.",
31
+ nargs="?",
32
+ default=None,
33
+ )
34
+ parser.add_argument(
35
+ "-o",
36
+ "--output-file",
37
+ type=str,
38
+ help="Output font file name.",
39
+ )
40
+ parser.add_argument(
41
+ "-v", "--verbose", action="store_true", help="Run more verbosely."
42
+ )
43
+
44
+ options = parser.parse_args(args)
45
+
46
+ configLogger(level=("INFO" if options.verbose else "WARNING"))
47
+
48
+ font = TTFont(options.font)
49
+
50
+ if options.designspace is None:
51
+ from .unbuild import unbuild
52
+
53
+ unbuild(font)
54
+ return 0
55
+
56
+ from .build import build
57
+
58
+ build(font, options.designspace)
59
+
60
+ if options.output_file is None:
61
+ outfile = makeOutputFileName(options.font, overWrite=True, suffix=".avar")
62
+ else:
63
+ outfile = options.output_file
64
+ if outfile:
65
+ log.info("Saving %s", outfile)
66
+ font.save(outfile)
67
+
68
+
69
+ if __name__ == "__main__":
70
+ import sys
71
+
72
+ sys.exit(main())
@@ -0,0 +1,79 @@
1
+ from fontTools.varLib import _add_fvar, _add_avar, load_designspace
2
+ from fontTools.ttLib import newTable
3
+ import logging
4
+
5
+ log = logging.getLogger("fontTools.varLib.avar")
6
+
7
+
8
+ def build(font, designspace_file):
9
+ ds = load_designspace(designspace_file, require_sources=False)
10
+
11
+ if not "fvar" in font:
12
+ # if "name" not in font:
13
+ font["name"] = newTable("name")
14
+ _add_fvar(font, ds.axes, ds.instances)
15
+
16
+ axisTags = [a.axisTag for a in font["fvar"].axes]
17
+
18
+ if "avar" in font:
19
+ log.warning("avar table already present, overwriting.")
20
+ del font["avar"]
21
+
22
+ _add_avar(font, ds.axes, ds.axisMappings, axisTags)
23
+
24
+
25
+ def main(args=None):
26
+ """Add `avar` table from designspace file to variable font."""
27
+
28
+ from fontTools.ttLib import TTFont
29
+ from fontTools.misc.cliTools import makeOutputFileName
30
+ from fontTools import configLogger
31
+ import argparse
32
+
33
+ if args is None:
34
+ import sys
35
+
36
+ args = sys.argv[1:]
37
+
38
+ parser = argparse.ArgumentParser(
39
+ "fonttools varLib.avar.build",
40
+ description="Add `avar` table from designspace file to variable font.",
41
+ )
42
+ parser.add_argument("font", metavar="varfont.ttf", help="Variable-font file.")
43
+ parser.add_argument(
44
+ "designspace",
45
+ metavar="family.designspace",
46
+ help="Designspace file.",
47
+ default=None,
48
+ )
49
+ parser.add_argument(
50
+ "-o",
51
+ "--output-file",
52
+ type=str,
53
+ help="Output font file name.",
54
+ )
55
+ parser.add_argument(
56
+ "-v", "--verbose", action="store_true", help="Run more verbosely."
57
+ )
58
+
59
+ options = parser.parse_args(args)
60
+
61
+ configLogger(level=("INFO" if options.verbose else "WARNING"))
62
+
63
+ font = TTFont(options.font)
64
+
65
+ build(font, options.designspace)
66
+
67
+ if options.output_file is None:
68
+ outfile = makeOutputFileName(options.font, overWrite=True, suffix=".avar")
69
+ else:
70
+ outfile = options.output_file
71
+ if outfile:
72
+ log.info("Saving %s", outfile)
73
+ font.save(outfile)
74
+
75
+
76
+ if __name__ == "__main__":
77
+ import sys
78
+
79
+ sys.exit(main())