fonttools 4.60.2__cp311-cp311-win32.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 +8 -0
- fontTools/__main__.py +35 -0
- fontTools/afmLib.py +439 -0
- fontTools/agl.py +5233 -0
- fontTools/annotations.py +30 -0
- fontTools/cffLib/CFF2ToCFF.py +258 -0
- fontTools/cffLib/CFFToCFF2.py +305 -0
- fontTools/cffLib/__init__.py +3694 -0
- fontTools/cffLib/specializer.py +927 -0
- fontTools/cffLib/transforms.py +495 -0
- fontTools/cffLib/width.py +210 -0
- fontTools/colorLib/__init__.py +0 -0
- fontTools/colorLib/builder.py +664 -0
- fontTools/colorLib/errors.py +2 -0
- fontTools/colorLib/geometry.py +143 -0
- fontTools/colorLib/table_builder.py +223 -0
- fontTools/colorLib/unbuilder.py +81 -0
- fontTools/config/__init__.py +90 -0
- fontTools/cu2qu/__init__.py +15 -0
- fontTools/cu2qu/__main__.py +6 -0
- fontTools/cu2qu/benchmark.py +54 -0
- fontTools/cu2qu/cli.py +198 -0
- fontTools/cu2qu/cu2qu.c +15817 -0
- fontTools/cu2qu/cu2qu.cp311-win32.pyd +0 -0
- fontTools/cu2qu/cu2qu.py +563 -0
- fontTools/cu2qu/errors.py +77 -0
- fontTools/cu2qu/ufo.py +363 -0
- fontTools/designspaceLib/__init__.py +3343 -0
- fontTools/designspaceLib/__main__.py +6 -0
- fontTools/designspaceLib/split.py +475 -0
- fontTools/designspaceLib/statNames.py +260 -0
- fontTools/designspaceLib/types.py +147 -0
- fontTools/encodings/MacRoman.py +258 -0
- fontTools/encodings/StandardEncoding.py +258 -0
- fontTools/encodings/__init__.py +1 -0
- fontTools/encodings/codecs.py +135 -0
- fontTools/feaLib/__init__.py +4 -0
- fontTools/feaLib/__main__.py +78 -0
- fontTools/feaLib/ast.py +2143 -0
- fontTools/feaLib/builder.py +1814 -0
- fontTools/feaLib/error.py +22 -0
- fontTools/feaLib/lexer.c +17029 -0
- fontTools/feaLib/lexer.cp311-win32.pyd +0 -0
- fontTools/feaLib/lexer.py +287 -0
- fontTools/feaLib/location.py +12 -0
- fontTools/feaLib/lookupDebugInfo.py +12 -0
- fontTools/feaLib/parser.py +2394 -0
- fontTools/feaLib/variableScalar.py +118 -0
- fontTools/fontBuilder.py +1014 -0
- fontTools/help.py +36 -0
- fontTools/merge/__init__.py +248 -0
- fontTools/merge/__main__.py +6 -0
- fontTools/merge/base.py +81 -0
- fontTools/merge/cmap.py +173 -0
- fontTools/merge/layout.py +526 -0
- fontTools/merge/options.py +85 -0
- fontTools/merge/tables.py +352 -0
- fontTools/merge/unicode.py +78 -0
- fontTools/merge/util.py +143 -0
- fontTools/misc/__init__.py +1 -0
- fontTools/misc/arrayTools.py +424 -0
- fontTools/misc/bezierTools.c +39731 -0
- fontTools/misc/bezierTools.cp311-win32.pyd +0 -0
- fontTools/misc/bezierTools.py +1500 -0
- fontTools/misc/classifyTools.py +170 -0
- fontTools/misc/cliTools.py +53 -0
- fontTools/misc/configTools.py +349 -0
- fontTools/misc/cython.py +27 -0
- fontTools/misc/dictTools.py +83 -0
- fontTools/misc/eexec.py +119 -0
- fontTools/misc/encodingTools.py +72 -0
- fontTools/misc/enumTools.py +23 -0
- fontTools/misc/etree.py +456 -0
- fontTools/misc/filenames.py +245 -0
- 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 +253 -0
- fontTools/misc/intTools.py +25 -0
- fontTools/misc/iterTools.py +12 -0
- fontTools/misc/lazyTools.py +42 -0
- fontTools/misc/loggingTools.py +543 -0
- fontTools/misc/macCreatorType.py +56 -0
- fontTools/misc/macRes.py +261 -0
- fontTools/misc/plistlib/__init__.py +681 -0
- fontTools/misc/plistlib/py.typed +0 -0
- fontTools/misc/psCharStrings.py +1511 -0
- fontTools/misc/psLib.py +398 -0
- fontTools/misc/psOperators.py +572 -0
- fontTools/misc/py23.py +96 -0
- fontTools/misc/roundTools.py +110 -0
- fontTools/misc/sstruct.py +227 -0
- fontTools/misc/symfont.py +242 -0
- fontTools/misc/testTools.py +233 -0
- fontTools/misc/textTools.py +156 -0
- fontTools/misc/timeTools.py +88 -0
- fontTools/misc/transform.py +516 -0
- fontTools/misc/treeTools.py +45 -0
- fontTools/misc/vector.py +147 -0
- fontTools/misc/visitor.py +158 -0
- fontTools/misc/xmlReader.py +188 -0
- fontTools/misc/xmlWriter.py +231 -0
- fontTools/mtiLib/__init__.py +1400 -0
- fontTools/mtiLib/__main__.py +5 -0
- fontTools/otlLib/__init__.py +1 -0
- fontTools/otlLib/builder.py +3465 -0
- fontTools/otlLib/error.py +11 -0
- fontTools/otlLib/maxContextCalc.py +96 -0
- fontTools/otlLib/optimize/__init__.py +53 -0
- fontTools/otlLib/optimize/__main__.py +6 -0
- fontTools/otlLib/optimize/gpos.py +439 -0
- fontTools/pens/__init__.py +1 -0
- fontTools/pens/areaPen.py +52 -0
- fontTools/pens/basePen.py +475 -0
- fontTools/pens/boundsPen.py +98 -0
- fontTools/pens/cairoPen.py +26 -0
- fontTools/pens/cocoaPen.py +26 -0
- fontTools/pens/cu2quPen.py +325 -0
- fontTools/pens/explicitClosingLinePen.py +101 -0
- fontTools/pens/filterPen.py +433 -0
- fontTools/pens/freetypePen.py +462 -0
- fontTools/pens/hashPointPen.py +89 -0
- fontTools/pens/momentsPen.c +13378 -0
- fontTools/pens/momentsPen.cp311-win32.pyd +0 -0
- fontTools/pens/momentsPen.py +879 -0
- fontTools/pens/perimeterPen.py +69 -0
- fontTools/pens/pointInsidePen.py +192 -0
- fontTools/pens/pointPen.py +643 -0
- fontTools/pens/qtPen.py +29 -0
- fontTools/pens/qu2cuPen.py +105 -0
- fontTools/pens/quartzPen.py +43 -0
- fontTools/pens/recordingPen.py +335 -0
- fontTools/pens/reportLabPen.py +79 -0
- fontTools/pens/reverseContourPen.py +96 -0
- fontTools/pens/roundingPen.py +130 -0
- fontTools/pens/statisticsPen.py +312 -0
- fontTools/pens/svgPathPen.py +310 -0
- fontTools/pens/t2CharStringPen.py +88 -0
- fontTools/pens/teePen.py +55 -0
- fontTools/pens/transformPen.py +115 -0
- fontTools/pens/ttGlyphPen.py +335 -0
- fontTools/pens/wxPen.py +29 -0
- fontTools/qu2cu/__init__.py +15 -0
- fontTools/qu2cu/__main__.py +7 -0
- fontTools/qu2cu/benchmark.py +56 -0
- fontTools/qu2cu/cli.py +125 -0
- fontTools/qu2cu/qu2cu.c +16682 -0
- fontTools/qu2cu/qu2cu.cp311-win32.pyd +0 -0
- fontTools/qu2cu/qu2cu.py +405 -0
- fontTools/subset/__init__.py +4096 -0
- fontTools/subset/__main__.py +6 -0
- fontTools/subset/cff.py +184 -0
- fontTools/subset/svg.py +253 -0
- fontTools/subset/util.py +25 -0
- fontTools/svgLib/__init__.py +3 -0
- fontTools/svgLib/path/__init__.py +65 -0
- fontTools/svgLib/path/arc.py +154 -0
- fontTools/svgLib/path/parser.py +322 -0
- fontTools/svgLib/path/shapes.py +183 -0
- fontTools/t1Lib/__init__.py +648 -0
- fontTools/tfmLib.py +460 -0
- fontTools/ttLib/__init__.py +30 -0
- fontTools/ttLib/__main__.py +148 -0
- fontTools/ttLib/macUtils.py +54 -0
- fontTools/ttLib/removeOverlaps.py +395 -0
- fontTools/ttLib/reorderGlyphs.py +285 -0
- fontTools/ttLib/scaleUpem.py +436 -0
- fontTools/ttLib/sfnt.py +661 -0
- fontTools/ttLib/standardGlyphOrder.py +271 -0
- fontTools/ttLib/tables/B_A_S_E_.py +14 -0
- fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
- fontTools/ttLib/tables/C_B_D_T_.py +113 -0
- fontTools/ttLib/tables/C_B_L_C_.py +19 -0
- fontTools/ttLib/tables/C_F_F_.py +61 -0
- fontTools/ttLib/tables/C_F_F__2.py +26 -0
- fontTools/ttLib/tables/C_O_L_R_.py +165 -0
- fontTools/ttLib/tables/C_P_A_L_.py +305 -0
- fontTools/ttLib/tables/D_S_I_G_.py +158 -0
- fontTools/ttLib/tables/D__e_b_g.py +35 -0
- fontTools/ttLib/tables/DefaultTable.py +49 -0
- fontTools/ttLib/tables/E_B_D_T_.py +835 -0
- fontTools/ttLib/tables/E_B_L_C_.py +718 -0
- fontTools/ttLib/tables/F_F_T_M_.py +52 -0
- fontTools/ttLib/tables/F__e_a_t.py +149 -0
- fontTools/ttLib/tables/G_D_E_F_.py +13 -0
- fontTools/ttLib/tables/G_M_A_P_.py +148 -0
- fontTools/ttLib/tables/G_P_K_G_.py +133 -0
- fontTools/ttLib/tables/G_P_O_S_.py +14 -0
- fontTools/ttLib/tables/G_S_U_B_.py +13 -0
- fontTools/ttLib/tables/G_V_A_R_.py +5 -0
- fontTools/ttLib/tables/G__l_a_t.py +235 -0
- fontTools/ttLib/tables/G__l_o_c.py +85 -0
- fontTools/ttLib/tables/H_V_A_R_.py +13 -0
- fontTools/ttLib/tables/J_S_T_F_.py +13 -0
- fontTools/ttLib/tables/L_T_S_H_.py +58 -0
- fontTools/ttLib/tables/M_A_T_H_.py +13 -0
- fontTools/ttLib/tables/M_E_T_A_.py +352 -0
- fontTools/ttLib/tables/M_V_A_R_.py +13 -0
- fontTools/ttLib/tables/O_S_2f_2.py +752 -0
- fontTools/ttLib/tables/S_I_N_G_.py +99 -0
- fontTools/ttLib/tables/S_T_A_T_.py +15 -0
- fontTools/ttLib/tables/S_V_G_.py +223 -0
- fontTools/ttLib/tables/S__i_l_f.py +1040 -0
- fontTools/ttLib/tables/S__i_l_l.py +92 -0
- fontTools/ttLib/tables/T_S_I_B_.py +13 -0
- fontTools/ttLib/tables/T_S_I_C_.py +14 -0
- fontTools/ttLib/tables/T_S_I_D_.py +13 -0
- fontTools/ttLib/tables/T_S_I_J_.py +13 -0
- fontTools/ttLib/tables/T_S_I_P_.py +13 -0
- fontTools/ttLib/tables/T_S_I_S_.py +13 -0
- fontTools/ttLib/tables/T_S_I_V_.py +26 -0
- fontTools/ttLib/tables/T_S_I__0.py +70 -0
- fontTools/ttLib/tables/T_S_I__1.py +163 -0
- fontTools/ttLib/tables/T_S_I__2.py +17 -0
- fontTools/ttLib/tables/T_S_I__3.py +22 -0
- fontTools/ttLib/tables/T_S_I__5.py +60 -0
- fontTools/ttLib/tables/T_T_F_A_.py +14 -0
- fontTools/ttLib/tables/TupleVariation.py +884 -0
- fontTools/ttLib/tables/V_A_R_C_.py +12 -0
- fontTools/ttLib/tables/V_D_M_X_.py +249 -0
- fontTools/ttLib/tables/V_O_R_G_.py +165 -0
- fontTools/ttLib/tables/V_V_A_R_.py +13 -0
- fontTools/ttLib/tables/__init__.py +98 -0
- fontTools/ttLib/tables/_a_n_k_r.py +15 -0
- fontTools/ttLib/tables/_a_v_a_r.py +193 -0
- fontTools/ttLib/tables/_b_s_l_n.py +15 -0
- fontTools/ttLib/tables/_c_i_d_g.py +24 -0
- fontTools/ttLib/tables/_c_m_a_p.py +1591 -0
- fontTools/ttLib/tables/_c_v_a_r.py +94 -0
- fontTools/ttLib/tables/_c_v_t.py +56 -0
- fontTools/ttLib/tables/_f_e_a_t.py +15 -0
- fontTools/ttLib/tables/_f_p_g_m.py +62 -0
- fontTools/ttLib/tables/_f_v_a_r.py +261 -0
- fontTools/ttLib/tables/_g_a_s_p.py +63 -0
- fontTools/ttLib/tables/_g_c_i_d.py +13 -0
- fontTools/ttLib/tables/_g_l_y_f.py +2311 -0
- fontTools/ttLib/tables/_g_v_a_r.py +340 -0
- fontTools/ttLib/tables/_h_d_m_x.py +127 -0
- fontTools/ttLib/tables/_h_e_a_d.py +130 -0
- fontTools/ttLib/tables/_h_h_e_a.py +147 -0
- fontTools/ttLib/tables/_h_m_t_x.py +164 -0
- fontTools/ttLib/tables/_k_e_r_n.py +289 -0
- fontTools/ttLib/tables/_l_c_a_r.py +13 -0
- fontTools/ttLib/tables/_l_o_c_a.py +70 -0
- fontTools/ttLib/tables/_l_t_a_g.py +72 -0
- fontTools/ttLib/tables/_m_a_x_p.py +147 -0
- fontTools/ttLib/tables/_m_e_t_a.py +112 -0
- fontTools/ttLib/tables/_m_o_r_t.py +14 -0
- fontTools/ttLib/tables/_m_o_r_x.py +15 -0
- fontTools/ttLib/tables/_n_a_m_e.py +1242 -0
- fontTools/ttLib/tables/_o_p_b_d.py +14 -0
- fontTools/ttLib/tables/_p_o_s_t.py +319 -0
- fontTools/ttLib/tables/_p_r_e_p.py +16 -0
- fontTools/ttLib/tables/_p_r_o_p.py +12 -0
- fontTools/ttLib/tables/_s_b_i_x.py +129 -0
- fontTools/ttLib/tables/_t_r_a_k.py +332 -0
- fontTools/ttLib/tables/_v_h_e_a.py +139 -0
- fontTools/ttLib/tables/_v_m_t_x.py +19 -0
- fontTools/ttLib/tables/asciiTable.py +20 -0
- fontTools/ttLib/tables/grUtils.py +92 -0
- fontTools/ttLib/tables/otBase.py +1458 -0
- fontTools/ttLib/tables/otConverters.py +2068 -0
- fontTools/ttLib/tables/otData.py +6400 -0
- fontTools/ttLib/tables/otTables.py +2703 -0
- fontTools/ttLib/tables/otTraverse.py +163 -0
- fontTools/ttLib/tables/sbixGlyph.py +149 -0
- fontTools/ttLib/tables/sbixStrike.py +177 -0
- fontTools/ttLib/tables/table_API_readme.txt +91 -0
- fontTools/ttLib/tables/ttProgram.py +594 -0
- fontTools/ttLib/ttCollection.py +125 -0
- fontTools/ttLib/ttFont.py +1148 -0
- fontTools/ttLib/ttGlyphSet.py +490 -0
- fontTools/ttLib/ttVisitor.py +32 -0
- fontTools/ttLib/woff2.py +1680 -0
- fontTools/ttx.py +479 -0
- fontTools/ufoLib/__init__.py +2575 -0
- fontTools/ufoLib/converters.py +407 -0
- fontTools/ufoLib/errors.py +30 -0
- fontTools/ufoLib/etree.py +6 -0
- fontTools/ufoLib/filenames.py +356 -0
- fontTools/ufoLib/glifLib.py +2120 -0
- fontTools/ufoLib/kerning.py +141 -0
- fontTools/ufoLib/plistlib.py +47 -0
- fontTools/ufoLib/pointPen.py +6 -0
- fontTools/ufoLib/utils.py +107 -0
- fontTools/ufoLib/validators.py +1208 -0
- fontTools/unicode.py +50 -0
- fontTools/unicodedata/Blocks.py +817 -0
- fontTools/unicodedata/Mirrored.py +446 -0
- fontTools/unicodedata/OTTags.py +50 -0
- fontTools/unicodedata/ScriptExtensions.py +832 -0
- fontTools/unicodedata/Scripts.py +3639 -0
- fontTools/unicodedata/__init__.py +306 -0
- fontTools/varLib/__init__.py +1600 -0
- fontTools/varLib/__main__.py +6 -0
- 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/unbuild.py +271 -0
- fontTools/varLib/avarPlanner.py +8 -0
- fontTools/varLib/builder.py +215 -0
- fontTools/varLib/cff.py +631 -0
- fontTools/varLib/errors.py +219 -0
- fontTools/varLib/featureVars.py +703 -0
- fontTools/varLib/hvar.py +113 -0
- fontTools/varLib/instancer/__init__.py +2052 -0
- fontTools/varLib/instancer/__main__.py +5 -0
- fontTools/varLib/instancer/featureVars.py +190 -0
- fontTools/varLib/instancer/names.py +388 -0
- fontTools/varLib/instancer/solver.py +309 -0
- fontTools/varLib/interpolatable.py +1209 -0
- fontTools/varLib/interpolatableHelpers.py +399 -0
- fontTools/varLib/interpolatablePlot.py +1269 -0
- fontTools/varLib/interpolatableTestContourOrder.py +82 -0
- fontTools/varLib/interpolatableTestStartingPoint.py +107 -0
- fontTools/varLib/interpolate_layout.py +124 -0
- fontTools/varLib/iup.c +19815 -0
- fontTools/varLib/iup.cp311-win32.pyd +0 -0
- fontTools/varLib/iup.py +490 -0
- fontTools/varLib/merger.py +1717 -0
- fontTools/varLib/models.py +642 -0
- fontTools/varLib/multiVarStore.py +253 -0
- fontTools/varLib/mutator.py +529 -0
- fontTools/varLib/mvar.py +40 -0
- fontTools/varLib/plot.py +238 -0
- fontTools/varLib/stat.py +149 -0
- fontTools/varLib/varStore.py +739 -0
- fontTools/voltLib/__init__.py +5 -0
- fontTools/voltLib/__main__.py +206 -0
- fontTools/voltLib/ast.py +452 -0
- fontTools/voltLib/error.py +12 -0
- fontTools/voltLib/lexer.py +99 -0
- fontTools/voltLib/parser.py +664 -0
- fontTools/voltLib/voltToFea.py +911 -0
- fonttools-4.60.2.data/data/share/man/man1/ttx.1 +225 -0
- fonttools-4.60.2.dist-info/METADATA +2250 -0
- fonttools-4.60.2.dist-info/RECORD +353 -0
- fonttools-4.60.2.dist-info/WHEEL +5 -0
- fonttools-4.60.2.dist-info/entry_points.txt +5 -0
- fonttools-4.60.2.dist-info/licenses/LICENSE +21 -0
- fonttools-4.60.2.dist-info/licenses/LICENSE.external +388 -0
- fonttools-4.60.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from fontTools.varLib.models import VariationModel
|
|
2
|
+
from fontTools.varLib.varStore import VarStoreInstancer
|
|
3
|
+
from fontTools.misc.fixedTools import fixedToFloat as fi2fl
|
|
4
|
+
from itertools import product
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _denormalize(v, axis):
|
|
9
|
+
if v >= 0:
|
|
10
|
+
return axis.defaultValue + v * (axis.maxValue - axis.defaultValue)
|
|
11
|
+
else:
|
|
12
|
+
return axis.defaultValue + v * (axis.defaultValue - axis.minValue)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _pruneLocations(locations, poles, axisTags):
|
|
16
|
+
# Now we have all the input locations, find which ones are
|
|
17
|
+
# not needed and remove them.
|
|
18
|
+
|
|
19
|
+
# Note: This algorithm is heavily tied to how VariationModel
|
|
20
|
+
# is implemented. It assumes that input was extracted from
|
|
21
|
+
# VariationModel-generated object, like an ItemVariationStore
|
|
22
|
+
# created by fontmake using varLib.models.VariationModel.
|
|
23
|
+
# Some CoPilot blabbering:
|
|
24
|
+
# I *think* I can prove that this algorithm is correct, but
|
|
25
|
+
# I'm not 100% sure. It's possible that there are edge cases
|
|
26
|
+
# where this algorithm will fail. I'm not sure how to prove
|
|
27
|
+
# that it's correct, but I'm also not sure how to prove that
|
|
28
|
+
# it's incorrect. I'm not sure how to write a test case that
|
|
29
|
+
# would prove that it's incorrect. I'm not sure how to write
|
|
30
|
+
# a test case that would prove that it's correct.
|
|
31
|
+
|
|
32
|
+
model = VariationModel(locations, axisTags)
|
|
33
|
+
modelMapping = model.mapping
|
|
34
|
+
modelSupports = model.supports
|
|
35
|
+
pins = {tuple(k.items()): None for k in poles}
|
|
36
|
+
for location in poles:
|
|
37
|
+
i = locations.index(location)
|
|
38
|
+
i = modelMapping[i]
|
|
39
|
+
support = modelSupports[i]
|
|
40
|
+
supportAxes = set(support.keys())
|
|
41
|
+
for axisTag, (minV, _, maxV) in support.items():
|
|
42
|
+
for v in (minV, maxV):
|
|
43
|
+
if v in (-1, 0, 1):
|
|
44
|
+
continue
|
|
45
|
+
for pin in pins.keys():
|
|
46
|
+
pinLocation = dict(pin)
|
|
47
|
+
pinAxes = set(pinLocation.keys())
|
|
48
|
+
if pinAxes != supportAxes:
|
|
49
|
+
continue
|
|
50
|
+
if axisTag not in pinAxes:
|
|
51
|
+
continue
|
|
52
|
+
if pinLocation[axisTag] == v:
|
|
53
|
+
break
|
|
54
|
+
else:
|
|
55
|
+
# No pin found. Go through the previous masters
|
|
56
|
+
# and find a suitable pin. Going backwards is
|
|
57
|
+
# better because it can find a pin that is close
|
|
58
|
+
# to the pole in more dimensions, and reducing
|
|
59
|
+
# the total number of pins needed.
|
|
60
|
+
for candidateIdx in range(i - 1, -1, -1):
|
|
61
|
+
candidate = modelSupports[candidateIdx]
|
|
62
|
+
candidateAxes = set(candidate.keys())
|
|
63
|
+
if candidateAxes != supportAxes:
|
|
64
|
+
continue
|
|
65
|
+
if axisTag not in candidateAxes:
|
|
66
|
+
continue
|
|
67
|
+
candidate = {
|
|
68
|
+
k: defaultV for k, (_, defaultV, _) in candidate.items()
|
|
69
|
+
}
|
|
70
|
+
if candidate[axisTag] == v:
|
|
71
|
+
pins[tuple(candidate.items())] = None
|
|
72
|
+
break
|
|
73
|
+
else:
|
|
74
|
+
assert False, "No pin found"
|
|
75
|
+
return [dict(t) for t in pins.keys()]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def mappings_from_avar(font, denormalize=True):
|
|
79
|
+
fvarAxes = font["fvar"].axes
|
|
80
|
+
axisMap = {a.axisTag: a for a in fvarAxes}
|
|
81
|
+
axisTags = [a.axisTag for a in fvarAxes]
|
|
82
|
+
axisIndexes = {a.axisTag: i for i, a in enumerate(fvarAxes)}
|
|
83
|
+
if "avar" not in font:
|
|
84
|
+
return {}, {}
|
|
85
|
+
avar = font["avar"]
|
|
86
|
+
axisMaps = {
|
|
87
|
+
tag: seg
|
|
88
|
+
for tag, seg in avar.segments.items()
|
|
89
|
+
if seg and seg != {-1: -1, 0: 0, 1: 1}
|
|
90
|
+
}
|
|
91
|
+
mappings = []
|
|
92
|
+
|
|
93
|
+
if getattr(avar, "majorVersion", 1) == 2:
|
|
94
|
+
varStore = avar.table.VarStore
|
|
95
|
+
regions = varStore.VarRegionList.Region
|
|
96
|
+
|
|
97
|
+
# Find all the input locations; this finds "poles", that are
|
|
98
|
+
# locations of the peaks, and "corners", that are locations
|
|
99
|
+
# of the corners of the regions. These two sets of locations
|
|
100
|
+
# together constitute inputLocations to consider.
|
|
101
|
+
|
|
102
|
+
poles = {(): None} # Just using it as an ordered set
|
|
103
|
+
inputLocations = set({()})
|
|
104
|
+
for varData in varStore.VarData:
|
|
105
|
+
regionIndices = varData.VarRegionIndex
|
|
106
|
+
for regionIndex in regionIndices:
|
|
107
|
+
peakLocation = []
|
|
108
|
+
corners = []
|
|
109
|
+
region = regions[regionIndex]
|
|
110
|
+
for axisIndex, axis in enumerate(region.VarRegionAxis):
|
|
111
|
+
if axis.PeakCoord == 0:
|
|
112
|
+
continue
|
|
113
|
+
axisTag = axisTags[axisIndex]
|
|
114
|
+
peakLocation.append((axisTag, axis.PeakCoord))
|
|
115
|
+
corner = []
|
|
116
|
+
if axis.StartCoord != 0:
|
|
117
|
+
corner.append((axisTag, axis.StartCoord))
|
|
118
|
+
if axis.EndCoord != 0:
|
|
119
|
+
corner.append((axisTag, axis.EndCoord))
|
|
120
|
+
corners.append(corner)
|
|
121
|
+
corners = set(product(*corners))
|
|
122
|
+
peakLocation = tuple(peakLocation)
|
|
123
|
+
poles[peakLocation] = None
|
|
124
|
+
inputLocations.add(peakLocation)
|
|
125
|
+
inputLocations.update(corners)
|
|
126
|
+
|
|
127
|
+
# Sort them by number of axes, then by axis order
|
|
128
|
+
inputLocations = [
|
|
129
|
+
dict(t)
|
|
130
|
+
for t in sorted(
|
|
131
|
+
inputLocations,
|
|
132
|
+
key=lambda t: (len(t), tuple(axisIndexes[tag] for tag, _ in t)),
|
|
133
|
+
)
|
|
134
|
+
]
|
|
135
|
+
poles = [dict(t) for t in poles.keys()]
|
|
136
|
+
inputLocations = _pruneLocations(inputLocations, list(poles), axisTags)
|
|
137
|
+
|
|
138
|
+
# Find the output locations, at input locations
|
|
139
|
+
varIdxMap = avar.table.VarIdxMap
|
|
140
|
+
instancer = VarStoreInstancer(varStore, fvarAxes)
|
|
141
|
+
for location in inputLocations:
|
|
142
|
+
instancer.setLocation(location)
|
|
143
|
+
outputLocation = {}
|
|
144
|
+
for axisIndex, axisTag in enumerate(axisTags):
|
|
145
|
+
varIdx = axisIndex
|
|
146
|
+
if varIdxMap is not None:
|
|
147
|
+
varIdx = varIdxMap[varIdx]
|
|
148
|
+
delta = instancer[varIdx]
|
|
149
|
+
if delta != 0:
|
|
150
|
+
v = location.get(axisTag, 0)
|
|
151
|
+
v = v + fi2fl(delta, 14)
|
|
152
|
+
# See https://github.com/fonttools/fonttools/pull/3598#issuecomment-2266082009
|
|
153
|
+
# v = max(-1, min(1, v))
|
|
154
|
+
outputLocation[axisTag] = v
|
|
155
|
+
mappings.append((location, outputLocation))
|
|
156
|
+
|
|
157
|
+
# Remove base master we added, if it maps to the default location
|
|
158
|
+
assert mappings[0][0] == {}
|
|
159
|
+
if mappings[0][1] == {}:
|
|
160
|
+
mappings.pop(0)
|
|
161
|
+
|
|
162
|
+
if denormalize:
|
|
163
|
+
for tag, seg in axisMaps.items():
|
|
164
|
+
if tag not in axisMap:
|
|
165
|
+
raise ValueError(f"Unknown axis tag {tag}")
|
|
166
|
+
denorm = lambda v: _denormalize(v, axisMap[tag])
|
|
167
|
+
axisMaps[tag] = {denorm(k): denorm(v) for k, v in seg.items()}
|
|
168
|
+
|
|
169
|
+
for i, (inputLoc, outputLoc) in enumerate(mappings):
|
|
170
|
+
inputLoc = {
|
|
171
|
+
tag: _denormalize(val, axisMap[tag]) for tag, val in inputLoc.items()
|
|
172
|
+
}
|
|
173
|
+
outputLoc = {
|
|
174
|
+
tag: _denormalize(val, axisMap[tag]) for tag, val in outputLoc.items()
|
|
175
|
+
}
|
|
176
|
+
mappings[i] = (inputLoc, outputLoc)
|
|
177
|
+
|
|
178
|
+
return axisMaps, mappings
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def unbuild(font, f=sys.stdout):
|
|
182
|
+
fvar = font["fvar"]
|
|
183
|
+
axes = fvar.axes
|
|
184
|
+
segments, mappings = mappings_from_avar(font)
|
|
185
|
+
|
|
186
|
+
if "name" in font:
|
|
187
|
+
name = font["name"]
|
|
188
|
+
axisNames = {axis.axisTag: name.getDebugName(axis.axisNameID) for axis in axes}
|
|
189
|
+
else:
|
|
190
|
+
axisNames = {a.axisTag: a.axisTag for a in axes}
|
|
191
|
+
|
|
192
|
+
print("<?xml version='1.0' encoding='UTF-8'?>", file=f)
|
|
193
|
+
print('<designspace format="5.1">', file=f)
|
|
194
|
+
print(" <axes>", file=f)
|
|
195
|
+
for axis in axes:
|
|
196
|
+
|
|
197
|
+
axisName = axisNames[axis.axisTag]
|
|
198
|
+
|
|
199
|
+
triplet = (axis.minValue, axis.defaultValue, axis.maxValue)
|
|
200
|
+
triplet = [int(v) if v == int(v) else v for v in triplet]
|
|
201
|
+
|
|
202
|
+
axisMap = segments.get(axis.axisTag)
|
|
203
|
+
closing = "/>" if axisMap is None else ">"
|
|
204
|
+
|
|
205
|
+
print(
|
|
206
|
+
f' <axis tag="{axis.axisTag}" name="{axisName}" minimum="{triplet[0]}" maximum="{triplet[2]}" default="{triplet[1]}"{closing}',
|
|
207
|
+
file=f,
|
|
208
|
+
)
|
|
209
|
+
if axisMap is not None:
|
|
210
|
+
for k in sorted(axisMap.keys()):
|
|
211
|
+
v = axisMap[k]
|
|
212
|
+
k = int(k) if k == int(k) else k
|
|
213
|
+
v = int(v) if v == int(v) else v
|
|
214
|
+
print(f' <map input="{k}" output="{v}"/>', file=f)
|
|
215
|
+
print(" </axis>", file=f)
|
|
216
|
+
if mappings:
|
|
217
|
+
print(" <mappings>", file=f)
|
|
218
|
+
for inputLoc, outputLoc in mappings:
|
|
219
|
+
print(" <mapping>", file=f)
|
|
220
|
+
print(" <input>", file=f)
|
|
221
|
+
for tag in sorted(inputLoc.keys()):
|
|
222
|
+
v = inputLoc[tag]
|
|
223
|
+
v = int(v) if v == int(v) else v
|
|
224
|
+
print(
|
|
225
|
+
f' <dimension name="{axisNames[tag]}" xvalue="{v}"/>',
|
|
226
|
+
file=f,
|
|
227
|
+
)
|
|
228
|
+
print(" </input>", file=f)
|
|
229
|
+
print(" <output>", file=f)
|
|
230
|
+
for tag in sorted(outputLoc.keys()):
|
|
231
|
+
v = outputLoc[tag]
|
|
232
|
+
v = int(v) if v == int(v) else v
|
|
233
|
+
print(
|
|
234
|
+
f' <dimension name="{axisNames[tag]}" xvalue="{v}"/>',
|
|
235
|
+
file=f,
|
|
236
|
+
)
|
|
237
|
+
print(" </output>", file=f)
|
|
238
|
+
print(" </mapping>", file=f)
|
|
239
|
+
print(" </mappings>", file=f)
|
|
240
|
+
print(" </axes>", file=f)
|
|
241
|
+
print("</designspace>", file=f)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def main(args=None):
|
|
245
|
+
"""Print `avar` table as a designspace snippet."""
|
|
246
|
+
|
|
247
|
+
if args is None:
|
|
248
|
+
args = sys.argv[1:]
|
|
249
|
+
|
|
250
|
+
from fontTools.ttLib import TTFont
|
|
251
|
+
import argparse
|
|
252
|
+
|
|
253
|
+
parser = argparse.ArgumentParser(
|
|
254
|
+
"fonttools varLib.avar.unbuild",
|
|
255
|
+
description="Print `avar` table as a designspace snippet.",
|
|
256
|
+
)
|
|
257
|
+
parser.add_argument("font", metavar="varfont.ttf", help="Variable-font file.")
|
|
258
|
+
options = parser.parse_args(args)
|
|
259
|
+
|
|
260
|
+
font = TTFont(options.font)
|
|
261
|
+
if "fvar" not in font:
|
|
262
|
+
print("Not a variable font.", file=sys.stderr)
|
|
263
|
+
return 1
|
|
264
|
+
|
|
265
|
+
unbuild(font)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
import sys
|
|
270
|
+
|
|
271
|
+
sys.exit(main())
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from fontTools import ttLib
|
|
2
|
+
from fontTools.ttLib.tables import otTables as ot
|
|
3
|
+
|
|
4
|
+
# VariationStore
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def buildVarRegionAxis(axisSupport):
|
|
8
|
+
self = ot.VarRegionAxis()
|
|
9
|
+
self.StartCoord, self.PeakCoord, self.EndCoord = [float(v) for v in axisSupport]
|
|
10
|
+
return self
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def buildSparseVarRegionAxis(axisIndex, axisSupport):
|
|
14
|
+
self = ot.SparseVarRegionAxis()
|
|
15
|
+
self.AxisIndex = axisIndex
|
|
16
|
+
self.StartCoord, self.PeakCoord, self.EndCoord = [float(v) for v in axisSupport]
|
|
17
|
+
return self
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def buildVarRegion(support, axisTags):
|
|
21
|
+
assert all(tag in axisTags for tag in support.keys()), (
|
|
22
|
+
"Unknown axis tag found.",
|
|
23
|
+
support,
|
|
24
|
+
axisTags,
|
|
25
|
+
)
|
|
26
|
+
self = ot.VarRegion()
|
|
27
|
+
self.VarRegionAxis = []
|
|
28
|
+
for tag in axisTags:
|
|
29
|
+
self.VarRegionAxis.append(buildVarRegionAxis(support.get(tag, (0, 0, 0))))
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def buildSparseVarRegion(support, axisTags):
|
|
34
|
+
assert all(tag in axisTags for tag in support.keys()), (
|
|
35
|
+
"Unknown axis tag found.",
|
|
36
|
+
support,
|
|
37
|
+
axisTags,
|
|
38
|
+
)
|
|
39
|
+
self = ot.SparseVarRegion()
|
|
40
|
+
self.SparseVarRegionAxis = []
|
|
41
|
+
for i, tag in enumerate(axisTags):
|
|
42
|
+
if tag not in support:
|
|
43
|
+
continue
|
|
44
|
+
self.SparseVarRegionAxis.append(
|
|
45
|
+
buildSparseVarRegionAxis(i, support.get(tag, (0, 0, 0)))
|
|
46
|
+
)
|
|
47
|
+
self.SparseRegionCount = len(self.SparseVarRegionAxis)
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def buildVarRegionList(supports, axisTags):
|
|
52
|
+
self = ot.VarRegionList()
|
|
53
|
+
self.RegionAxisCount = len(axisTags)
|
|
54
|
+
self.Region = []
|
|
55
|
+
for support in supports:
|
|
56
|
+
self.Region.append(buildVarRegion(support, axisTags))
|
|
57
|
+
self.RegionCount = len(self.Region)
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def buildSparseVarRegionList(supports, axisTags):
|
|
62
|
+
self = ot.SparseVarRegionList()
|
|
63
|
+
self.RegionAxisCount = len(axisTags)
|
|
64
|
+
self.Region = []
|
|
65
|
+
for support in supports:
|
|
66
|
+
self.Region.append(buildSparseVarRegion(support, axisTags))
|
|
67
|
+
self.RegionCount = len(self.Region)
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _reorderItem(lst, mapping):
|
|
72
|
+
return [lst[i] for i in mapping]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def VarData_calculateNumShorts(self, optimize=False):
|
|
76
|
+
count = self.VarRegionCount
|
|
77
|
+
items = self.Item
|
|
78
|
+
bit_lengths = [0] * count
|
|
79
|
+
for item in items:
|
|
80
|
+
# The "+ (i < -1)" magic is to handle two's-compliment.
|
|
81
|
+
# That is, we want to get back 7 for -128, whereas
|
|
82
|
+
# bit_length() returns 8. Similarly for -65536.
|
|
83
|
+
# The reason "i < -1" is used instead of "i < 0" is that
|
|
84
|
+
# the latter would make it return 0 for "-1" instead of 1.
|
|
85
|
+
bl = [(i + (i < -1)).bit_length() for i in item]
|
|
86
|
+
bit_lengths = [max(*pair) for pair in zip(bl, bit_lengths)]
|
|
87
|
+
# The addition of 8, instead of seven, is to account for the sign bit.
|
|
88
|
+
# This "((b + 8) >> 3) if b else 0" when combined with the above
|
|
89
|
+
# "(i + (i < -1)).bit_length()" is a faster way to compute byte-lengths
|
|
90
|
+
# conforming to:
|
|
91
|
+
#
|
|
92
|
+
# byte_length = (0 if i == 0 else
|
|
93
|
+
# 1 if -128 <= i < 128 else
|
|
94
|
+
# 2 if -65536 <= i < 65536 else
|
|
95
|
+
# ...)
|
|
96
|
+
byte_lengths = [((b + 8) >> 3) if b else 0 for b in bit_lengths]
|
|
97
|
+
|
|
98
|
+
# https://github.com/fonttools/fonttools/issues/2279
|
|
99
|
+
longWords = any(b > 2 for b in byte_lengths)
|
|
100
|
+
|
|
101
|
+
if optimize:
|
|
102
|
+
# Reorder columns such that wider columns come before narrower columns
|
|
103
|
+
mapping = []
|
|
104
|
+
mapping.extend(i for i, b in enumerate(byte_lengths) if b > 2)
|
|
105
|
+
mapping.extend(i for i, b in enumerate(byte_lengths) if b == 2)
|
|
106
|
+
mapping.extend(i for i, b in enumerate(byte_lengths) if b == 1)
|
|
107
|
+
|
|
108
|
+
byte_lengths = _reorderItem(byte_lengths, mapping)
|
|
109
|
+
self.VarRegionIndex = _reorderItem(self.VarRegionIndex, mapping)
|
|
110
|
+
self.VarRegionCount = len(self.VarRegionIndex)
|
|
111
|
+
for i in range(len(items)):
|
|
112
|
+
items[i] = _reorderItem(items[i], mapping)
|
|
113
|
+
|
|
114
|
+
if longWords:
|
|
115
|
+
self.NumShorts = (
|
|
116
|
+
max((i for i, b in enumerate(byte_lengths) if b > 2), default=-1) + 1
|
|
117
|
+
)
|
|
118
|
+
self.NumShorts |= 0x8000
|
|
119
|
+
else:
|
|
120
|
+
self.NumShorts = (
|
|
121
|
+
max((i for i, b in enumerate(byte_lengths) if b > 1), default=-1) + 1
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
self.VarRegionCount = len(self.VarRegionIndex)
|
|
125
|
+
return self
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
ot.VarData.calculateNumShorts = VarData_calculateNumShorts
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def VarData_CalculateNumShorts(self, optimize=True):
|
|
132
|
+
"""Deprecated name for VarData_calculateNumShorts() which
|
|
133
|
+
defaults to optimize=True. Use varData.calculateNumShorts()
|
|
134
|
+
or varData.optimize()."""
|
|
135
|
+
return VarData_calculateNumShorts(self, optimize=optimize)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def VarData_optimize(self):
|
|
139
|
+
return VarData_calculateNumShorts(self, optimize=True)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
ot.VarData.optimize = VarData_optimize
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def buildVarData(varRegionIndices, items, optimize=True):
|
|
146
|
+
self = ot.VarData()
|
|
147
|
+
self.VarRegionIndex = list(varRegionIndices)
|
|
148
|
+
regionCount = self.VarRegionCount = len(self.VarRegionIndex)
|
|
149
|
+
records = self.Item = []
|
|
150
|
+
if items:
|
|
151
|
+
for item in items:
|
|
152
|
+
assert len(item) == regionCount
|
|
153
|
+
records.append(list(item))
|
|
154
|
+
self.ItemCount = len(self.Item)
|
|
155
|
+
self.calculateNumShorts(optimize=optimize)
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def buildVarStore(varRegionList, varDataList):
|
|
160
|
+
self = ot.VarStore()
|
|
161
|
+
self.Format = 1
|
|
162
|
+
self.VarRegionList = varRegionList
|
|
163
|
+
self.VarData = list(varDataList)
|
|
164
|
+
self.VarDataCount = len(self.VarData)
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def buildMultiVarData(varRegionIndices, items):
|
|
169
|
+
self = ot.MultiVarData()
|
|
170
|
+
self.Format = 1
|
|
171
|
+
self.VarRegionIndex = list(varRegionIndices)
|
|
172
|
+
regionCount = self.VarRegionCount = len(self.VarRegionIndex)
|
|
173
|
+
records = self.Item = []
|
|
174
|
+
if items:
|
|
175
|
+
for item in items:
|
|
176
|
+
assert len(item) == regionCount
|
|
177
|
+
records.append(list(item))
|
|
178
|
+
self.ItemCount = len(self.Item)
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def buildMultiVarStore(varRegionList, multiVarDataList):
|
|
183
|
+
self = ot.MultiVarStore()
|
|
184
|
+
self.Format = 1
|
|
185
|
+
self.SparseVarRegionList = varRegionList
|
|
186
|
+
self.MultiVarData = list(multiVarDataList)
|
|
187
|
+
self.MultiVarDataCount = len(self.MultiVarData)
|
|
188
|
+
return self
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Variation helpers
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def buildVarIdxMap(varIdxes, glyphOrder):
|
|
195
|
+
self = ot.VarIdxMap()
|
|
196
|
+
self.mapping = {g: v for g, v in zip(glyphOrder, varIdxes)}
|
|
197
|
+
return self
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def buildDeltaSetIndexMap(varIdxes):
|
|
201
|
+
mapping = list(varIdxes)
|
|
202
|
+
if all(i == v for i, v in enumerate(mapping)):
|
|
203
|
+
return None
|
|
204
|
+
self = ot.DeltaSetIndexMap()
|
|
205
|
+
self.mapping = mapping
|
|
206
|
+
self.Format = 1 if len(mapping) > 0xFFFF else 0
|
|
207
|
+
return self
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def buildVarDevTable(varIdx):
|
|
211
|
+
self = ot.Device()
|
|
212
|
+
self.DeltaFormat = 0x8000
|
|
213
|
+
self.StartSize = varIdx >> 16
|
|
214
|
+
self.EndSize = varIdx & 0xFFFF
|
|
215
|
+
return self
|