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,1400 @@
|
|
|
1
|
+
# FontDame-to-FontTools for OpenType Layout tables
|
|
2
|
+
#
|
|
3
|
+
# Source language spec is available at:
|
|
4
|
+
# http://monotype.github.io/OpenType_Table_Source/otl_source.html
|
|
5
|
+
# https://github.com/Monotype/OpenType_Table_Source/
|
|
6
|
+
|
|
7
|
+
from fontTools import ttLib
|
|
8
|
+
from fontTools.ttLib.tables._c_m_a_p import cmap_classes
|
|
9
|
+
from fontTools.ttLib.tables import otTables as ot
|
|
10
|
+
from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
|
|
11
|
+
from fontTools.otlLib import builder as otl
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
from fontTools.ttLib import newTable
|
|
14
|
+
from fontTools.feaLib.lookupDebugInfo import LOOKUP_DEBUG_ENV_VAR, LOOKUP_DEBUG_INFO_KEY
|
|
15
|
+
from operator import setitem
|
|
16
|
+
import os
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MtiLibError(Exception):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ReferenceNotFoundError(MtiLibError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FeatureNotFoundError(ReferenceNotFoundError):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LookupNotFoundError(ReferenceNotFoundError):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
log = logging.getLogger("fontTools.mtiLib")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def makeGlyph(s):
|
|
40
|
+
if s[:2] in ["U ", "u "]:
|
|
41
|
+
return ttLib.TTFont._makeGlyphName(int(s[2:], 16))
|
|
42
|
+
elif s[:2] == "# ":
|
|
43
|
+
return "glyph%.5d" % int(s[2:])
|
|
44
|
+
assert s.find(" ") < 0, "Space found in glyph name: %s" % s
|
|
45
|
+
assert s, "Glyph name is empty"
|
|
46
|
+
return s
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def makeGlyphs(l):
|
|
50
|
+
return [makeGlyph(g) for g in l]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def mapLookup(sym, mapping):
|
|
54
|
+
# Lookups are addressed by name. So resolved them using a map if available.
|
|
55
|
+
# Fallback to parsing as lookup index if a map isn't provided.
|
|
56
|
+
if mapping is not None:
|
|
57
|
+
try:
|
|
58
|
+
idx = mapping[sym]
|
|
59
|
+
except KeyError:
|
|
60
|
+
raise LookupNotFoundError(sym)
|
|
61
|
+
else:
|
|
62
|
+
idx = int(sym)
|
|
63
|
+
return idx
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def mapFeature(sym, mapping):
|
|
67
|
+
# Features are referenced by index according the spec. So, if symbol is an
|
|
68
|
+
# integer, use it directly. Otherwise look up in the map if provided.
|
|
69
|
+
try:
|
|
70
|
+
idx = int(sym)
|
|
71
|
+
except ValueError:
|
|
72
|
+
try:
|
|
73
|
+
idx = mapping[sym]
|
|
74
|
+
except KeyError:
|
|
75
|
+
raise FeatureNotFoundError(sym)
|
|
76
|
+
return idx
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def setReference(mapper, mapping, sym, setter, collection, key):
|
|
80
|
+
try:
|
|
81
|
+
mapped = mapper(sym, mapping)
|
|
82
|
+
except ReferenceNotFoundError as e:
|
|
83
|
+
try:
|
|
84
|
+
if mapping is not None:
|
|
85
|
+
mapping.addDeferredMapping(
|
|
86
|
+
lambda ref: setter(collection, key, ref), sym, e
|
|
87
|
+
)
|
|
88
|
+
return
|
|
89
|
+
except AttributeError:
|
|
90
|
+
pass
|
|
91
|
+
raise
|
|
92
|
+
setter(collection, key, mapped)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class DeferredMapping(dict):
|
|
96
|
+
def __init__(self):
|
|
97
|
+
self._deferredMappings = []
|
|
98
|
+
|
|
99
|
+
def addDeferredMapping(self, setter, sym, e):
|
|
100
|
+
log.debug("Adding deferred mapping for symbol '%s' %s", sym, type(e).__name__)
|
|
101
|
+
self._deferredMappings.append((setter, sym, e))
|
|
102
|
+
|
|
103
|
+
def applyDeferredMappings(self):
|
|
104
|
+
for setter, sym, e in self._deferredMappings:
|
|
105
|
+
log.debug(
|
|
106
|
+
"Applying deferred mapping for symbol '%s' %s", sym, type(e).__name__
|
|
107
|
+
)
|
|
108
|
+
try:
|
|
109
|
+
mapped = self[sym]
|
|
110
|
+
except KeyError:
|
|
111
|
+
raise e
|
|
112
|
+
setter(mapped)
|
|
113
|
+
log.debug("Set to %s", mapped)
|
|
114
|
+
self._deferredMappings = []
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def parseScriptList(lines, featureMap=None):
|
|
118
|
+
self = ot.ScriptList()
|
|
119
|
+
records = []
|
|
120
|
+
with lines.between("script table"):
|
|
121
|
+
for line in lines:
|
|
122
|
+
while len(line) < 4:
|
|
123
|
+
line.append("")
|
|
124
|
+
scriptTag, langSysTag, defaultFeature, features = line
|
|
125
|
+
log.debug("Adding script %s language-system %s", scriptTag, langSysTag)
|
|
126
|
+
|
|
127
|
+
langSys = ot.LangSys()
|
|
128
|
+
langSys.LookupOrder = None
|
|
129
|
+
if defaultFeature:
|
|
130
|
+
setReference(
|
|
131
|
+
mapFeature,
|
|
132
|
+
featureMap,
|
|
133
|
+
defaultFeature,
|
|
134
|
+
setattr,
|
|
135
|
+
langSys,
|
|
136
|
+
"ReqFeatureIndex",
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
langSys.ReqFeatureIndex = 0xFFFF
|
|
140
|
+
syms = stripSplitComma(features)
|
|
141
|
+
langSys.FeatureIndex = theList = [3] * len(syms)
|
|
142
|
+
for i, sym in enumerate(syms):
|
|
143
|
+
setReference(mapFeature, featureMap, sym, setitem, theList, i)
|
|
144
|
+
langSys.FeatureCount = len(langSys.FeatureIndex)
|
|
145
|
+
|
|
146
|
+
script = [s for s in records if s.ScriptTag == scriptTag]
|
|
147
|
+
if script:
|
|
148
|
+
script = script[0].Script
|
|
149
|
+
else:
|
|
150
|
+
scriptRec = ot.ScriptRecord()
|
|
151
|
+
scriptRec.ScriptTag = scriptTag + " " * (4 - len(scriptTag))
|
|
152
|
+
scriptRec.Script = ot.Script()
|
|
153
|
+
records.append(scriptRec)
|
|
154
|
+
script = scriptRec.Script
|
|
155
|
+
script.DefaultLangSys = None
|
|
156
|
+
script.LangSysRecord = []
|
|
157
|
+
script.LangSysCount = 0
|
|
158
|
+
|
|
159
|
+
if langSysTag == "default":
|
|
160
|
+
script.DefaultLangSys = langSys
|
|
161
|
+
else:
|
|
162
|
+
langSysRec = ot.LangSysRecord()
|
|
163
|
+
langSysRec.LangSysTag = langSysTag + " " * (4 - len(langSysTag))
|
|
164
|
+
langSysRec.LangSys = langSys
|
|
165
|
+
script.LangSysRecord.append(langSysRec)
|
|
166
|
+
script.LangSysCount = len(script.LangSysRecord)
|
|
167
|
+
|
|
168
|
+
for script in records:
|
|
169
|
+
script.Script.LangSysRecord = sorted(
|
|
170
|
+
script.Script.LangSysRecord, key=lambda rec: rec.LangSysTag
|
|
171
|
+
)
|
|
172
|
+
self.ScriptRecord = sorted(records, key=lambda rec: rec.ScriptTag)
|
|
173
|
+
self.ScriptCount = len(self.ScriptRecord)
|
|
174
|
+
return self
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def parseFeatureList(lines, lookupMap=None, featureMap=None):
|
|
178
|
+
self = ot.FeatureList()
|
|
179
|
+
self.FeatureRecord = []
|
|
180
|
+
with lines.between("feature table"):
|
|
181
|
+
for line in lines:
|
|
182
|
+
name, featureTag, lookups = line
|
|
183
|
+
if featureMap is not None:
|
|
184
|
+
assert name not in featureMap, "Duplicate feature name: %s" % name
|
|
185
|
+
featureMap[name] = len(self.FeatureRecord)
|
|
186
|
+
# If feature name is integer, make sure it matches its index.
|
|
187
|
+
try:
|
|
188
|
+
assert int(name) == len(self.FeatureRecord), "%d %d" % (
|
|
189
|
+
name,
|
|
190
|
+
len(self.FeatureRecord),
|
|
191
|
+
)
|
|
192
|
+
except ValueError:
|
|
193
|
+
pass
|
|
194
|
+
featureRec = ot.FeatureRecord()
|
|
195
|
+
featureRec.FeatureTag = featureTag
|
|
196
|
+
featureRec.Feature = ot.Feature()
|
|
197
|
+
self.FeatureRecord.append(featureRec)
|
|
198
|
+
feature = featureRec.Feature
|
|
199
|
+
feature.FeatureParams = None
|
|
200
|
+
syms = stripSplitComma(lookups)
|
|
201
|
+
feature.LookupListIndex = theList = [None] * len(syms)
|
|
202
|
+
for i, sym in enumerate(syms):
|
|
203
|
+
setReference(mapLookup, lookupMap, sym, setitem, theList, i)
|
|
204
|
+
feature.LookupCount = len(feature.LookupListIndex)
|
|
205
|
+
|
|
206
|
+
self.FeatureCount = len(self.FeatureRecord)
|
|
207
|
+
return self
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def parseLookupFlags(lines):
|
|
211
|
+
flags = 0
|
|
212
|
+
filterset = None
|
|
213
|
+
allFlags = [
|
|
214
|
+
"righttoleft",
|
|
215
|
+
"ignorebaseglyphs",
|
|
216
|
+
"ignoreligatures",
|
|
217
|
+
"ignoremarks",
|
|
218
|
+
"markattachmenttype",
|
|
219
|
+
"markfiltertype",
|
|
220
|
+
]
|
|
221
|
+
while lines.peeks()[0].lower() in allFlags:
|
|
222
|
+
line = next(lines)
|
|
223
|
+
flag = {
|
|
224
|
+
"righttoleft": 0x0001,
|
|
225
|
+
"ignorebaseglyphs": 0x0002,
|
|
226
|
+
"ignoreligatures": 0x0004,
|
|
227
|
+
"ignoremarks": 0x0008,
|
|
228
|
+
}.get(line[0].lower())
|
|
229
|
+
if flag:
|
|
230
|
+
assert line[1].lower() in ["yes", "no"], line[1]
|
|
231
|
+
if line[1].lower() == "yes":
|
|
232
|
+
flags |= flag
|
|
233
|
+
continue
|
|
234
|
+
if line[0].lower() == "markattachmenttype":
|
|
235
|
+
flags |= int(line[1]) << 8
|
|
236
|
+
continue
|
|
237
|
+
if line[0].lower() == "markfiltertype":
|
|
238
|
+
flags |= 0x10
|
|
239
|
+
filterset = int(line[1])
|
|
240
|
+
return flags, filterset
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def parseSingleSubst(lines, font, _lookupMap=None):
|
|
244
|
+
mapping = {}
|
|
245
|
+
for line in lines:
|
|
246
|
+
assert len(line) == 2, line
|
|
247
|
+
line = makeGlyphs(line)
|
|
248
|
+
mapping[line[0]] = line[1]
|
|
249
|
+
return otl.buildSingleSubstSubtable(mapping)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def parseMultiple(lines, font, _lookupMap=None):
|
|
253
|
+
mapping = {}
|
|
254
|
+
for line in lines:
|
|
255
|
+
line = makeGlyphs(line)
|
|
256
|
+
mapping[line[0]] = line[1:]
|
|
257
|
+
return otl.buildMultipleSubstSubtable(mapping)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def parseAlternate(lines, font, _lookupMap=None):
|
|
261
|
+
mapping = {}
|
|
262
|
+
for line in lines:
|
|
263
|
+
line = makeGlyphs(line)
|
|
264
|
+
mapping[line[0]] = line[1:]
|
|
265
|
+
return otl.buildAlternateSubstSubtable(mapping)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def parseLigature(lines, font, _lookupMap=None):
|
|
269
|
+
mapping = {}
|
|
270
|
+
for line in lines:
|
|
271
|
+
assert len(line) >= 2, line
|
|
272
|
+
line = makeGlyphs(line)
|
|
273
|
+
mapping[tuple(line[1:])] = line[0]
|
|
274
|
+
return otl.buildLigatureSubstSubtable(mapping)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def parseSinglePos(lines, font, _lookupMap=None):
|
|
278
|
+
values = {}
|
|
279
|
+
for line in lines:
|
|
280
|
+
assert len(line) == 3, line
|
|
281
|
+
w = line[0].title().replace(" ", "")
|
|
282
|
+
assert w in valueRecordFormatDict
|
|
283
|
+
g = makeGlyph(line[1])
|
|
284
|
+
v = int(line[2])
|
|
285
|
+
if g not in values:
|
|
286
|
+
values[g] = ValueRecord()
|
|
287
|
+
assert not hasattr(values[g], w), (g, w)
|
|
288
|
+
setattr(values[g], w, v)
|
|
289
|
+
return otl.buildSinglePosSubtable(values, font.getReverseGlyphMap())
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def parsePair(lines, font, _lookupMap=None):
|
|
293
|
+
self = ot.PairPos()
|
|
294
|
+
self.ValueFormat1 = self.ValueFormat2 = 0
|
|
295
|
+
typ = lines.peeks()[0].split()[0].lower()
|
|
296
|
+
if typ in ("left", "right"):
|
|
297
|
+
self.Format = 1
|
|
298
|
+
values = {}
|
|
299
|
+
for line in lines:
|
|
300
|
+
assert len(line) == 4, line
|
|
301
|
+
side = line[0].split()[0].lower()
|
|
302
|
+
assert side in ("left", "right"), side
|
|
303
|
+
what = line[0][len(side) :].title().replace(" ", "")
|
|
304
|
+
mask = valueRecordFormatDict[what][0]
|
|
305
|
+
glyph1, glyph2 = makeGlyphs(line[1:3])
|
|
306
|
+
value = int(line[3])
|
|
307
|
+
if not glyph1 in values:
|
|
308
|
+
values[glyph1] = {}
|
|
309
|
+
if not glyph2 in values[glyph1]:
|
|
310
|
+
values[glyph1][glyph2] = (ValueRecord(), ValueRecord())
|
|
311
|
+
rec2 = values[glyph1][glyph2]
|
|
312
|
+
if side == "left":
|
|
313
|
+
self.ValueFormat1 |= mask
|
|
314
|
+
vr = rec2[0]
|
|
315
|
+
else:
|
|
316
|
+
self.ValueFormat2 |= mask
|
|
317
|
+
vr = rec2[1]
|
|
318
|
+
assert not hasattr(vr, what), (vr, what)
|
|
319
|
+
setattr(vr, what, value)
|
|
320
|
+
self.Coverage = makeCoverage(set(values.keys()), font)
|
|
321
|
+
self.PairSet = []
|
|
322
|
+
for glyph1 in self.Coverage.glyphs:
|
|
323
|
+
values1 = values[glyph1]
|
|
324
|
+
pairset = ot.PairSet()
|
|
325
|
+
records = pairset.PairValueRecord = []
|
|
326
|
+
for glyph2 in sorted(values1.keys(), key=font.getGlyphID):
|
|
327
|
+
values2 = values1[glyph2]
|
|
328
|
+
pair = ot.PairValueRecord()
|
|
329
|
+
pair.SecondGlyph = glyph2
|
|
330
|
+
pair.Value1 = values2[0]
|
|
331
|
+
pair.Value2 = values2[1] if self.ValueFormat2 else None
|
|
332
|
+
records.append(pair)
|
|
333
|
+
pairset.PairValueCount = len(pairset.PairValueRecord)
|
|
334
|
+
self.PairSet.append(pairset)
|
|
335
|
+
self.PairSetCount = len(self.PairSet)
|
|
336
|
+
elif typ.endswith("class"):
|
|
337
|
+
self.Format = 2
|
|
338
|
+
classDefs = [None, None]
|
|
339
|
+
while lines.peeks()[0].endswith("class definition begin"):
|
|
340
|
+
typ = lines.peek()[0][: -len("class definition begin")].lower()
|
|
341
|
+
idx, klass = {
|
|
342
|
+
"first": (0, ot.ClassDef1),
|
|
343
|
+
"second": (1, ot.ClassDef2),
|
|
344
|
+
}[typ]
|
|
345
|
+
assert classDefs[idx] is None
|
|
346
|
+
classDefs[idx] = parseClassDef(lines, font, klass=klass)
|
|
347
|
+
self.ClassDef1, self.ClassDef2 = classDefs
|
|
348
|
+
self.Class1Count, self.Class2Count = (
|
|
349
|
+
1 + max(c.classDefs.values()) for c in classDefs
|
|
350
|
+
)
|
|
351
|
+
self.Class1Record = [ot.Class1Record() for i in range(self.Class1Count)]
|
|
352
|
+
for rec1 in self.Class1Record:
|
|
353
|
+
rec1.Class2Record = [ot.Class2Record() for j in range(self.Class2Count)]
|
|
354
|
+
for rec2 in rec1.Class2Record:
|
|
355
|
+
rec2.Value1 = ValueRecord()
|
|
356
|
+
rec2.Value2 = ValueRecord()
|
|
357
|
+
for line in lines:
|
|
358
|
+
assert len(line) == 4, line
|
|
359
|
+
side = line[0].split()[0].lower()
|
|
360
|
+
assert side in ("left", "right"), side
|
|
361
|
+
what = line[0][len(side) :].title().replace(" ", "")
|
|
362
|
+
mask = valueRecordFormatDict[what][0]
|
|
363
|
+
class1, class2, value = (int(x) for x in line[1:4])
|
|
364
|
+
rec2 = self.Class1Record[class1].Class2Record[class2]
|
|
365
|
+
if side == "left":
|
|
366
|
+
self.ValueFormat1 |= mask
|
|
367
|
+
vr = rec2.Value1
|
|
368
|
+
else:
|
|
369
|
+
self.ValueFormat2 |= mask
|
|
370
|
+
vr = rec2.Value2
|
|
371
|
+
assert not hasattr(vr, what), (vr, what)
|
|
372
|
+
setattr(vr, what, value)
|
|
373
|
+
for rec1 in self.Class1Record:
|
|
374
|
+
for rec2 in rec1.Class2Record:
|
|
375
|
+
rec2.Value1 = ValueRecord(self.ValueFormat1, rec2.Value1)
|
|
376
|
+
rec2.Value2 = (
|
|
377
|
+
ValueRecord(self.ValueFormat2, rec2.Value2)
|
|
378
|
+
if self.ValueFormat2
|
|
379
|
+
else None
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
self.Coverage = makeCoverage(set(self.ClassDef1.classDefs.keys()), font)
|
|
383
|
+
else:
|
|
384
|
+
assert 0, typ
|
|
385
|
+
return self
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def parseKernset(lines, font, _lookupMap=None):
|
|
389
|
+
typ = lines.peeks()[0].split()[0].lower()
|
|
390
|
+
if typ in ("left", "right"):
|
|
391
|
+
with lines.until(
|
|
392
|
+
("firstclass definition begin", "secondclass definition begin")
|
|
393
|
+
):
|
|
394
|
+
return parsePair(lines, font)
|
|
395
|
+
return parsePair(lines, font)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def makeAnchor(data, klass=ot.Anchor):
|
|
399
|
+
assert len(data) <= 2
|
|
400
|
+
anchor = klass()
|
|
401
|
+
anchor.Format = 1
|
|
402
|
+
anchor.XCoordinate, anchor.YCoordinate = intSplitComma(data[0])
|
|
403
|
+
if len(data) > 1 and data[1] != "":
|
|
404
|
+
anchor.Format = 2
|
|
405
|
+
anchor.AnchorPoint = int(data[1])
|
|
406
|
+
return anchor
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def parseCursive(lines, font, _lookupMap=None):
|
|
410
|
+
records = {}
|
|
411
|
+
for line in lines:
|
|
412
|
+
assert len(line) in [3, 4], line
|
|
413
|
+
idx, klass = {
|
|
414
|
+
"entry": (0, ot.EntryAnchor),
|
|
415
|
+
"exit": (1, ot.ExitAnchor),
|
|
416
|
+
}[line[0]]
|
|
417
|
+
glyph = makeGlyph(line[1])
|
|
418
|
+
if glyph not in records:
|
|
419
|
+
records[glyph] = [None, None]
|
|
420
|
+
assert records[glyph][idx] is None, (glyph, idx)
|
|
421
|
+
records[glyph][idx] = makeAnchor(line[2:], klass)
|
|
422
|
+
return otl.buildCursivePosSubtable(records, font.getReverseGlyphMap())
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def makeMarkRecords(data, coverage, c):
|
|
426
|
+
records = []
|
|
427
|
+
for glyph in coverage.glyphs:
|
|
428
|
+
klass, anchor = data[glyph]
|
|
429
|
+
record = c.MarkRecordClass()
|
|
430
|
+
record.Class = klass
|
|
431
|
+
setattr(record, c.MarkAnchor, anchor)
|
|
432
|
+
records.append(record)
|
|
433
|
+
return records
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def makeBaseRecords(data, coverage, c, classCount):
|
|
437
|
+
records = []
|
|
438
|
+
idx = {}
|
|
439
|
+
for glyph in coverage.glyphs:
|
|
440
|
+
idx[glyph] = len(records)
|
|
441
|
+
record = c.BaseRecordClass()
|
|
442
|
+
anchors = [None] * classCount
|
|
443
|
+
setattr(record, c.BaseAnchor, anchors)
|
|
444
|
+
records.append(record)
|
|
445
|
+
for (glyph, klass), anchor in data.items():
|
|
446
|
+
record = records[idx[glyph]]
|
|
447
|
+
anchors = getattr(record, c.BaseAnchor)
|
|
448
|
+
assert anchors[klass] is None, (glyph, klass)
|
|
449
|
+
anchors[klass] = anchor
|
|
450
|
+
return records
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def makeLigatureRecords(data, coverage, c, classCount):
|
|
454
|
+
records = [None] * len(coverage.glyphs)
|
|
455
|
+
idx = {g: i for i, g in enumerate(coverage.glyphs)}
|
|
456
|
+
|
|
457
|
+
for (glyph, klass, compIdx, compCount), anchor in data.items():
|
|
458
|
+
record = records[idx[glyph]]
|
|
459
|
+
if record is None:
|
|
460
|
+
record = records[idx[glyph]] = ot.LigatureAttach()
|
|
461
|
+
record.ComponentCount = compCount
|
|
462
|
+
record.ComponentRecord = [ot.ComponentRecord() for i in range(compCount)]
|
|
463
|
+
for compRec in record.ComponentRecord:
|
|
464
|
+
compRec.LigatureAnchor = [None] * classCount
|
|
465
|
+
assert record.ComponentCount == compCount, (
|
|
466
|
+
glyph,
|
|
467
|
+
record.ComponentCount,
|
|
468
|
+
compCount,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
anchors = record.ComponentRecord[compIdx - 1].LigatureAnchor
|
|
472
|
+
assert anchors[klass] is None, (glyph, compIdx, klass)
|
|
473
|
+
anchors[klass] = anchor
|
|
474
|
+
return records
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def parseMarkToSomething(lines, font, c):
|
|
478
|
+
self = c.Type()
|
|
479
|
+
self.Format = 1
|
|
480
|
+
markData = {}
|
|
481
|
+
baseData = {}
|
|
482
|
+
Data = {
|
|
483
|
+
"mark": (markData, c.MarkAnchorClass),
|
|
484
|
+
"base": (baseData, c.BaseAnchorClass),
|
|
485
|
+
"ligature": (baseData, c.BaseAnchorClass),
|
|
486
|
+
}
|
|
487
|
+
maxKlass = 0
|
|
488
|
+
for line in lines:
|
|
489
|
+
typ = line[0]
|
|
490
|
+
assert typ in ("mark", "base", "ligature")
|
|
491
|
+
glyph = makeGlyph(line[1])
|
|
492
|
+
data, anchorClass = Data[typ]
|
|
493
|
+
extraItems = 2 if typ == "ligature" else 0
|
|
494
|
+
extras = tuple(int(i) for i in line[2 : 2 + extraItems])
|
|
495
|
+
klass = int(line[2 + extraItems])
|
|
496
|
+
anchor = makeAnchor(line[3 + extraItems :], anchorClass)
|
|
497
|
+
if typ == "mark":
|
|
498
|
+
key, value = glyph, (klass, anchor)
|
|
499
|
+
else:
|
|
500
|
+
key, value = ((glyph, klass) + extras), anchor
|
|
501
|
+
assert key not in data, key
|
|
502
|
+
data[key] = value
|
|
503
|
+
maxKlass = max(maxKlass, klass)
|
|
504
|
+
|
|
505
|
+
# Mark
|
|
506
|
+
markCoverage = makeCoverage(set(markData.keys()), font, c.MarkCoverageClass)
|
|
507
|
+
markArray = c.MarkArrayClass()
|
|
508
|
+
markRecords = makeMarkRecords(markData, markCoverage, c)
|
|
509
|
+
setattr(markArray, c.MarkRecord, markRecords)
|
|
510
|
+
setattr(markArray, c.MarkCount, len(markRecords))
|
|
511
|
+
setattr(self, c.MarkCoverage, markCoverage)
|
|
512
|
+
setattr(self, c.MarkArray, markArray)
|
|
513
|
+
self.ClassCount = maxKlass + 1
|
|
514
|
+
|
|
515
|
+
# Base
|
|
516
|
+
self.classCount = 0 if not baseData else 1 + max(k[1] for k, v in baseData.items())
|
|
517
|
+
baseCoverage = makeCoverage(
|
|
518
|
+
set([k[0] for k in baseData.keys()]), font, c.BaseCoverageClass
|
|
519
|
+
)
|
|
520
|
+
baseArray = c.BaseArrayClass()
|
|
521
|
+
if c.Base == "Ligature":
|
|
522
|
+
baseRecords = makeLigatureRecords(baseData, baseCoverage, c, self.classCount)
|
|
523
|
+
else:
|
|
524
|
+
baseRecords = makeBaseRecords(baseData, baseCoverage, c, self.classCount)
|
|
525
|
+
setattr(baseArray, c.BaseRecord, baseRecords)
|
|
526
|
+
setattr(baseArray, c.BaseCount, len(baseRecords))
|
|
527
|
+
setattr(self, c.BaseCoverage, baseCoverage)
|
|
528
|
+
setattr(self, c.BaseArray, baseArray)
|
|
529
|
+
|
|
530
|
+
return self
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
class MarkHelper(object):
|
|
534
|
+
def __init__(self):
|
|
535
|
+
for Which in ("Mark", "Base"):
|
|
536
|
+
for What in ("Coverage", "Array", "Count", "Record", "Anchor"):
|
|
537
|
+
key = Which + What
|
|
538
|
+
if Which == "Mark" and What in ("Count", "Record", "Anchor"):
|
|
539
|
+
value = key
|
|
540
|
+
else:
|
|
541
|
+
value = getattr(self, Which) + What
|
|
542
|
+
if value == "LigatureRecord":
|
|
543
|
+
value = "LigatureAttach"
|
|
544
|
+
setattr(self, key, value)
|
|
545
|
+
if What != "Count":
|
|
546
|
+
klass = getattr(ot, value)
|
|
547
|
+
setattr(self, key + "Class", klass)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class MarkToBaseHelper(MarkHelper):
|
|
551
|
+
Mark = "Mark"
|
|
552
|
+
Base = "Base"
|
|
553
|
+
Type = ot.MarkBasePos
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
class MarkToMarkHelper(MarkHelper):
|
|
557
|
+
Mark = "Mark1"
|
|
558
|
+
Base = "Mark2"
|
|
559
|
+
Type = ot.MarkMarkPos
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class MarkToLigatureHelper(MarkHelper):
|
|
563
|
+
Mark = "Mark"
|
|
564
|
+
Base = "Ligature"
|
|
565
|
+
Type = ot.MarkLigPos
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def parseMarkToBase(lines, font, _lookupMap=None):
|
|
569
|
+
return parseMarkToSomething(lines, font, MarkToBaseHelper())
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def parseMarkToMark(lines, font, _lookupMap=None):
|
|
573
|
+
return parseMarkToSomething(lines, font, MarkToMarkHelper())
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def parseMarkToLigature(lines, font, _lookupMap=None):
|
|
577
|
+
return parseMarkToSomething(lines, font, MarkToLigatureHelper())
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def stripSplitComma(line):
|
|
581
|
+
return [s.strip() for s in line.split(",")] if line else []
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def intSplitComma(line):
|
|
585
|
+
return [int(i) for i in line.split(",")] if line else []
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
# Copied from fontTools.subset
|
|
589
|
+
class ContextHelper(object):
|
|
590
|
+
def __init__(self, klassName, Format):
|
|
591
|
+
if klassName.endswith("Subst"):
|
|
592
|
+
Typ = "Sub"
|
|
593
|
+
Type = "Subst"
|
|
594
|
+
else:
|
|
595
|
+
Typ = "Pos"
|
|
596
|
+
Type = "Pos"
|
|
597
|
+
if klassName.startswith("Chain"):
|
|
598
|
+
Chain = "Chain"
|
|
599
|
+
InputIdx = 1
|
|
600
|
+
DataLen = 3
|
|
601
|
+
else:
|
|
602
|
+
Chain = ""
|
|
603
|
+
InputIdx = 0
|
|
604
|
+
DataLen = 1
|
|
605
|
+
ChainTyp = Chain + Typ
|
|
606
|
+
|
|
607
|
+
self.Typ = Typ
|
|
608
|
+
self.Type = Type
|
|
609
|
+
self.Chain = Chain
|
|
610
|
+
self.ChainTyp = ChainTyp
|
|
611
|
+
self.InputIdx = InputIdx
|
|
612
|
+
self.DataLen = DataLen
|
|
613
|
+
|
|
614
|
+
self.LookupRecord = Type + "LookupRecord"
|
|
615
|
+
|
|
616
|
+
if Format == 1:
|
|
617
|
+
Coverage = lambda r: r.Coverage
|
|
618
|
+
ChainCoverage = lambda r: r.Coverage
|
|
619
|
+
ContextData = lambda r: (None,)
|
|
620
|
+
ChainContextData = lambda r: (None, None, None)
|
|
621
|
+
SetContextData = None
|
|
622
|
+
SetChainContextData = None
|
|
623
|
+
RuleData = lambda r: (r.Input,)
|
|
624
|
+
ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)
|
|
625
|
+
|
|
626
|
+
def SetRuleData(r, d):
|
|
627
|
+
(r.Input,) = d
|
|
628
|
+
(r.GlyphCount,) = (len(x) + 1 for x in d)
|
|
629
|
+
|
|
630
|
+
def ChainSetRuleData(r, d):
|
|
631
|
+
(r.Backtrack, r.Input, r.LookAhead) = d
|
|
632
|
+
(
|
|
633
|
+
r.BacktrackGlyphCount,
|
|
634
|
+
r.InputGlyphCount,
|
|
635
|
+
r.LookAheadGlyphCount,
|
|
636
|
+
) = (len(d[0]), len(d[1]) + 1, len(d[2]))
|
|
637
|
+
|
|
638
|
+
elif Format == 2:
|
|
639
|
+
Coverage = lambda r: r.Coverage
|
|
640
|
+
ChainCoverage = lambda r: r.Coverage
|
|
641
|
+
ContextData = lambda r: (r.ClassDef,)
|
|
642
|
+
ChainContextData = lambda r: (
|
|
643
|
+
r.BacktrackClassDef,
|
|
644
|
+
r.InputClassDef,
|
|
645
|
+
r.LookAheadClassDef,
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
def SetContextData(r, d):
|
|
649
|
+
(r.ClassDef,) = d
|
|
650
|
+
|
|
651
|
+
def SetChainContextData(r, d):
|
|
652
|
+
(r.BacktrackClassDef, r.InputClassDef, r.LookAheadClassDef) = d
|
|
653
|
+
|
|
654
|
+
RuleData = lambda r: (r.Class,)
|
|
655
|
+
ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)
|
|
656
|
+
|
|
657
|
+
def SetRuleData(r, d):
|
|
658
|
+
(r.Class,) = d
|
|
659
|
+
(r.GlyphCount,) = (len(x) + 1 for x in d)
|
|
660
|
+
|
|
661
|
+
def ChainSetRuleData(r, d):
|
|
662
|
+
(r.Backtrack, r.Input, r.LookAhead) = d
|
|
663
|
+
(
|
|
664
|
+
r.BacktrackGlyphCount,
|
|
665
|
+
r.InputGlyphCount,
|
|
666
|
+
r.LookAheadGlyphCount,
|
|
667
|
+
) = (len(d[0]), len(d[1]) + 1, len(d[2]))
|
|
668
|
+
|
|
669
|
+
elif Format == 3:
|
|
670
|
+
Coverage = lambda r: r.Coverage[0]
|
|
671
|
+
ChainCoverage = lambda r: r.InputCoverage[0]
|
|
672
|
+
ContextData = None
|
|
673
|
+
ChainContextData = None
|
|
674
|
+
SetContextData = None
|
|
675
|
+
SetChainContextData = None
|
|
676
|
+
RuleData = lambda r: r.Coverage
|
|
677
|
+
ChainRuleData = lambda r: (
|
|
678
|
+
r.BacktrackCoverage + r.InputCoverage + r.LookAheadCoverage
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
def SetRuleData(r, d):
|
|
682
|
+
(r.Coverage,) = d
|
|
683
|
+
(r.GlyphCount,) = (len(x) for x in d)
|
|
684
|
+
|
|
685
|
+
def ChainSetRuleData(r, d):
|
|
686
|
+
(r.BacktrackCoverage, r.InputCoverage, r.LookAheadCoverage) = d
|
|
687
|
+
(
|
|
688
|
+
r.BacktrackGlyphCount,
|
|
689
|
+
r.InputGlyphCount,
|
|
690
|
+
r.LookAheadGlyphCount,
|
|
691
|
+
) = (len(x) for x in d)
|
|
692
|
+
|
|
693
|
+
else:
|
|
694
|
+
assert 0, "unknown format: %s" % Format
|
|
695
|
+
|
|
696
|
+
if Chain:
|
|
697
|
+
self.Coverage = ChainCoverage
|
|
698
|
+
self.ContextData = ChainContextData
|
|
699
|
+
self.SetContextData = SetChainContextData
|
|
700
|
+
self.RuleData = ChainRuleData
|
|
701
|
+
self.SetRuleData = ChainSetRuleData
|
|
702
|
+
else:
|
|
703
|
+
self.Coverage = Coverage
|
|
704
|
+
self.ContextData = ContextData
|
|
705
|
+
self.SetContextData = SetContextData
|
|
706
|
+
self.RuleData = RuleData
|
|
707
|
+
self.SetRuleData = SetRuleData
|
|
708
|
+
|
|
709
|
+
if Format == 1:
|
|
710
|
+
self.Rule = ChainTyp + "Rule"
|
|
711
|
+
self.RuleCount = ChainTyp + "RuleCount"
|
|
712
|
+
self.RuleSet = ChainTyp + "RuleSet"
|
|
713
|
+
self.RuleSetCount = ChainTyp + "RuleSetCount"
|
|
714
|
+
self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
|
|
715
|
+
elif Format == 2:
|
|
716
|
+
self.Rule = ChainTyp + "ClassRule"
|
|
717
|
+
self.RuleCount = ChainTyp + "ClassRuleCount"
|
|
718
|
+
self.RuleSet = ChainTyp + "ClassSet"
|
|
719
|
+
self.RuleSetCount = ChainTyp + "ClassSetCount"
|
|
720
|
+
self.Intersect = lambda glyphs, c, r: (
|
|
721
|
+
c.intersect_class(glyphs, r)
|
|
722
|
+
if c
|
|
723
|
+
else (set(glyphs) if r == 0 else set())
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
self.ClassDef = "InputClassDef" if Chain else "ClassDef"
|
|
727
|
+
self.ClassDefIndex = 1 if Chain else 0
|
|
728
|
+
self.Input = "Input" if Chain else "Class"
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def parseLookupRecords(items, klassName, lookupMap=None):
|
|
732
|
+
klass = getattr(ot, klassName)
|
|
733
|
+
lst = []
|
|
734
|
+
for item in items:
|
|
735
|
+
rec = klass()
|
|
736
|
+
item = stripSplitComma(item)
|
|
737
|
+
assert len(item) == 2, item
|
|
738
|
+
idx = int(item[0])
|
|
739
|
+
assert idx > 0, idx
|
|
740
|
+
rec.SequenceIndex = idx - 1
|
|
741
|
+
setReference(mapLookup, lookupMap, item[1], setattr, rec, "LookupListIndex")
|
|
742
|
+
lst.append(rec)
|
|
743
|
+
return lst
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def makeClassDef(classDefs, font, klass=ot.Coverage):
|
|
747
|
+
if not classDefs:
|
|
748
|
+
return None
|
|
749
|
+
self = klass()
|
|
750
|
+
self.classDefs = dict(classDefs)
|
|
751
|
+
return self
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
def parseClassDef(lines, font, klass=ot.ClassDef):
|
|
755
|
+
classDefs = {}
|
|
756
|
+
with lines.between("class definition"):
|
|
757
|
+
for line in lines:
|
|
758
|
+
glyph = makeGlyph(line[0])
|
|
759
|
+
assert glyph not in classDefs, glyph
|
|
760
|
+
classDefs[glyph] = int(line[1])
|
|
761
|
+
return makeClassDef(classDefs, font, klass)
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def makeCoverage(glyphs, font, klass=ot.Coverage):
|
|
765
|
+
if not glyphs:
|
|
766
|
+
return None
|
|
767
|
+
if isinstance(glyphs, set):
|
|
768
|
+
glyphs = sorted(glyphs)
|
|
769
|
+
coverage = klass()
|
|
770
|
+
coverage.glyphs = sorted(set(glyphs), key=font.getGlyphID)
|
|
771
|
+
return coverage
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def parseCoverage(lines, font, klass=ot.Coverage):
|
|
775
|
+
glyphs = []
|
|
776
|
+
with lines.between("coverage definition"):
|
|
777
|
+
for line in lines:
|
|
778
|
+
glyphs.append(makeGlyph(line[0]))
|
|
779
|
+
return makeCoverage(glyphs, font, klass)
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def bucketizeRules(self, c, rules, bucketKeys):
|
|
783
|
+
buckets = {}
|
|
784
|
+
for seq, recs in rules:
|
|
785
|
+
buckets.setdefault(seq[c.InputIdx][0], []).append(
|
|
786
|
+
(tuple(s[1 if i == c.InputIdx else 0 :] for i, s in enumerate(seq)), recs)
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
rulesets = []
|
|
790
|
+
for firstGlyph in bucketKeys:
|
|
791
|
+
if firstGlyph not in buckets:
|
|
792
|
+
rulesets.append(None)
|
|
793
|
+
continue
|
|
794
|
+
thisRules = []
|
|
795
|
+
for seq, recs in buckets[firstGlyph]:
|
|
796
|
+
rule = getattr(ot, c.Rule)()
|
|
797
|
+
c.SetRuleData(rule, seq)
|
|
798
|
+
setattr(rule, c.Type + "Count", len(recs))
|
|
799
|
+
setattr(rule, c.LookupRecord, recs)
|
|
800
|
+
thisRules.append(rule)
|
|
801
|
+
|
|
802
|
+
ruleset = getattr(ot, c.RuleSet)()
|
|
803
|
+
setattr(ruleset, c.Rule, thisRules)
|
|
804
|
+
setattr(ruleset, c.RuleCount, len(thisRules))
|
|
805
|
+
rulesets.append(ruleset)
|
|
806
|
+
|
|
807
|
+
setattr(self, c.RuleSet, rulesets)
|
|
808
|
+
setattr(self, c.RuleSetCount, len(rulesets))
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
def parseContext(lines, font, Type, lookupMap=None):
|
|
812
|
+
self = getattr(ot, Type)()
|
|
813
|
+
typ = lines.peeks()[0].split()[0].lower()
|
|
814
|
+
if typ == "glyph":
|
|
815
|
+
self.Format = 1
|
|
816
|
+
log.debug("Parsing %s format %s", Type, self.Format)
|
|
817
|
+
c = ContextHelper(Type, self.Format)
|
|
818
|
+
rules = []
|
|
819
|
+
for line in lines:
|
|
820
|
+
assert line[0].lower() == "glyph", line[0]
|
|
821
|
+
while len(line) < 1 + c.DataLen:
|
|
822
|
+
line.append("")
|
|
823
|
+
seq = tuple(makeGlyphs(stripSplitComma(i)) for i in line[1 : 1 + c.DataLen])
|
|
824
|
+
recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
|
|
825
|
+
rules.append((seq, recs))
|
|
826
|
+
|
|
827
|
+
firstGlyphs = set(seq[c.InputIdx][0] for seq, recs in rules)
|
|
828
|
+
self.Coverage = makeCoverage(firstGlyphs, font)
|
|
829
|
+
bucketizeRules(self, c, rules, self.Coverage.glyphs)
|
|
830
|
+
elif typ.endswith("class"):
|
|
831
|
+
self.Format = 2
|
|
832
|
+
log.debug("Parsing %s format %s", Type, self.Format)
|
|
833
|
+
c = ContextHelper(Type, self.Format)
|
|
834
|
+
classDefs = [None] * c.DataLen
|
|
835
|
+
while lines.peeks()[0].endswith("class definition begin"):
|
|
836
|
+
typ = lines.peek()[0][: -len("class definition begin")].lower()
|
|
837
|
+
idx, klass = {
|
|
838
|
+
1: {
|
|
839
|
+
"": (0, ot.ClassDef),
|
|
840
|
+
},
|
|
841
|
+
3: {
|
|
842
|
+
"backtrack": (0, ot.BacktrackClassDef),
|
|
843
|
+
"": (1, ot.InputClassDef),
|
|
844
|
+
"lookahead": (2, ot.LookAheadClassDef),
|
|
845
|
+
},
|
|
846
|
+
}[c.DataLen][typ]
|
|
847
|
+
assert classDefs[idx] is None, idx
|
|
848
|
+
classDefs[idx] = parseClassDef(lines, font, klass=klass)
|
|
849
|
+
c.SetContextData(self, classDefs)
|
|
850
|
+
rules = []
|
|
851
|
+
for line in lines:
|
|
852
|
+
assert line[0].lower().startswith("class"), line[0]
|
|
853
|
+
while len(line) < 1 + c.DataLen:
|
|
854
|
+
line.append("")
|
|
855
|
+
seq = tuple(intSplitComma(i) for i in line[1 : 1 + c.DataLen])
|
|
856
|
+
recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
|
|
857
|
+
rules.append((seq, recs))
|
|
858
|
+
firstClasses = set(seq[c.InputIdx][0] for seq, recs in rules)
|
|
859
|
+
firstGlyphs = set(
|
|
860
|
+
g for g, c in classDefs[c.InputIdx].classDefs.items() if c in firstClasses
|
|
861
|
+
)
|
|
862
|
+
self.Coverage = makeCoverage(firstGlyphs, font)
|
|
863
|
+
bucketizeRules(self, c, rules, range(max(firstClasses) + 1))
|
|
864
|
+
elif typ.endswith("coverage"):
|
|
865
|
+
self.Format = 3
|
|
866
|
+
log.debug("Parsing %s format %s", Type, self.Format)
|
|
867
|
+
c = ContextHelper(Type, self.Format)
|
|
868
|
+
coverages = tuple([] for i in range(c.DataLen))
|
|
869
|
+
while lines.peeks()[0].endswith("coverage definition begin"):
|
|
870
|
+
typ = lines.peek()[0][: -len("coverage definition begin")].lower()
|
|
871
|
+
idx, klass = {
|
|
872
|
+
1: {
|
|
873
|
+
"": (0, ot.Coverage),
|
|
874
|
+
},
|
|
875
|
+
3: {
|
|
876
|
+
"backtrack": (0, ot.BacktrackCoverage),
|
|
877
|
+
"input": (1, ot.InputCoverage),
|
|
878
|
+
"lookahead": (2, ot.LookAheadCoverage),
|
|
879
|
+
},
|
|
880
|
+
}[c.DataLen][typ]
|
|
881
|
+
coverages[idx].append(parseCoverage(lines, font, klass=klass))
|
|
882
|
+
c.SetRuleData(self, coverages)
|
|
883
|
+
lines = list(lines)
|
|
884
|
+
assert len(lines) == 1
|
|
885
|
+
line = lines[0]
|
|
886
|
+
assert line[0].lower() == "coverage", line[0]
|
|
887
|
+
recs = parseLookupRecords(line[1:], c.LookupRecord, lookupMap)
|
|
888
|
+
setattr(self, c.Type + "Count", len(recs))
|
|
889
|
+
setattr(self, c.LookupRecord, recs)
|
|
890
|
+
else:
|
|
891
|
+
assert 0, typ
|
|
892
|
+
return self
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
def parseContextSubst(lines, font, lookupMap=None):
|
|
896
|
+
return parseContext(lines, font, "ContextSubst", lookupMap=lookupMap)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def parseContextPos(lines, font, lookupMap=None):
|
|
900
|
+
return parseContext(lines, font, "ContextPos", lookupMap=lookupMap)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def parseChainedSubst(lines, font, lookupMap=None):
|
|
904
|
+
return parseContext(lines, font, "ChainContextSubst", lookupMap=lookupMap)
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def parseChainedPos(lines, font, lookupMap=None):
|
|
908
|
+
return parseContext(lines, font, "ChainContextPos", lookupMap=lookupMap)
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
def parseReverseChainedSubst(lines, font, _lookupMap=None):
|
|
912
|
+
self = ot.ReverseChainSingleSubst()
|
|
913
|
+
self.Format = 1
|
|
914
|
+
coverages = ([], [])
|
|
915
|
+
while lines.peeks()[0].endswith("coverage definition begin"):
|
|
916
|
+
typ = lines.peek()[0][: -len("coverage definition begin")].lower()
|
|
917
|
+
idx, klass = {
|
|
918
|
+
"backtrack": (0, ot.BacktrackCoverage),
|
|
919
|
+
"lookahead": (1, ot.LookAheadCoverage),
|
|
920
|
+
}[typ]
|
|
921
|
+
coverages[idx].append(parseCoverage(lines, font, klass=klass))
|
|
922
|
+
self.BacktrackCoverage = coverages[0]
|
|
923
|
+
self.BacktrackGlyphCount = len(self.BacktrackCoverage)
|
|
924
|
+
self.LookAheadCoverage = coverages[1]
|
|
925
|
+
self.LookAheadGlyphCount = len(self.LookAheadCoverage)
|
|
926
|
+
mapping = {}
|
|
927
|
+
for line in lines:
|
|
928
|
+
assert len(line) == 2, line
|
|
929
|
+
line = makeGlyphs(line)
|
|
930
|
+
mapping[line[0]] = line[1]
|
|
931
|
+
self.Coverage = makeCoverage(set(mapping.keys()), font)
|
|
932
|
+
self.Substitute = [mapping[k] for k in self.Coverage.glyphs]
|
|
933
|
+
self.GlyphCount = len(self.Substitute)
|
|
934
|
+
return self
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def parseLookup(lines, tableTag, font, lookupMap=None):
|
|
938
|
+
line = lines.expect("lookup")
|
|
939
|
+
_, name, typ = line
|
|
940
|
+
log.debug("Parsing lookup type %s %s", typ, name)
|
|
941
|
+
lookup = ot.Lookup()
|
|
942
|
+
lookup.LookupFlag, filterset = parseLookupFlags(lines)
|
|
943
|
+
if filterset is not None:
|
|
944
|
+
lookup.MarkFilteringSet = filterset
|
|
945
|
+
lookup.LookupType, parseLookupSubTable = {
|
|
946
|
+
"GSUB": {
|
|
947
|
+
"single": (1, parseSingleSubst),
|
|
948
|
+
"multiple": (2, parseMultiple),
|
|
949
|
+
"alternate": (3, parseAlternate),
|
|
950
|
+
"ligature": (4, parseLigature),
|
|
951
|
+
"context": (5, parseContextSubst),
|
|
952
|
+
"chained": (6, parseChainedSubst),
|
|
953
|
+
"reversechained": (8, parseReverseChainedSubst),
|
|
954
|
+
},
|
|
955
|
+
"GPOS": {
|
|
956
|
+
"single": (1, parseSinglePos),
|
|
957
|
+
"pair": (2, parsePair),
|
|
958
|
+
"kernset": (2, parseKernset),
|
|
959
|
+
"cursive": (3, parseCursive),
|
|
960
|
+
"mark to base": (4, parseMarkToBase),
|
|
961
|
+
"mark to ligature": (5, parseMarkToLigature),
|
|
962
|
+
"mark to mark": (6, parseMarkToMark),
|
|
963
|
+
"context": (7, parseContextPos),
|
|
964
|
+
"chained": (8, parseChainedPos),
|
|
965
|
+
},
|
|
966
|
+
}[tableTag][typ]
|
|
967
|
+
|
|
968
|
+
with lines.until("lookup end"):
|
|
969
|
+
subtables = []
|
|
970
|
+
|
|
971
|
+
while lines.peek():
|
|
972
|
+
with lines.until(("% subtable", "subtable end")):
|
|
973
|
+
while lines.peek():
|
|
974
|
+
subtable = parseLookupSubTable(lines, font, lookupMap)
|
|
975
|
+
assert lookup.LookupType == subtable.LookupType
|
|
976
|
+
subtables.append(subtable)
|
|
977
|
+
if lines.peeks()[0] in ("% subtable", "subtable end"):
|
|
978
|
+
next(lines)
|
|
979
|
+
lines.expect("lookup end")
|
|
980
|
+
|
|
981
|
+
lookup.SubTable = subtables
|
|
982
|
+
lookup.SubTableCount = len(lookup.SubTable)
|
|
983
|
+
if lookup.SubTableCount == 0:
|
|
984
|
+
# Remove this return when following is fixed:
|
|
985
|
+
# https://github.com/fonttools/fonttools/issues/789
|
|
986
|
+
return None
|
|
987
|
+
return lookup
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def parseGSUBGPOS(lines, font, tableTag):
|
|
991
|
+
container = ttLib.getTableClass(tableTag)()
|
|
992
|
+
lookupMap = DeferredMapping()
|
|
993
|
+
featureMap = DeferredMapping()
|
|
994
|
+
assert tableTag in ("GSUB", "GPOS")
|
|
995
|
+
log.debug("Parsing %s", tableTag)
|
|
996
|
+
self = getattr(ot, tableTag)()
|
|
997
|
+
self.Version = 0x00010000
|
|
998
|
+
fields = {
|
|
999
|
+
"script table begin": (
|
|
1000
|
+
"ScriptList",
|
|
1001
|
+
lambda lines: parseScriptList(lines, featureMap),
|
|
1002
|
+
),
|
|
1003
|
+
"feature table begin": (
|
|
1004
|
+
"FeatureList",
|
|
1005
|
+
lambda lines: parseFeatureList(lines, lookupMap, featureMap),
|
|
1006
|
+
),
|
|
1007
|
+
"lookup": ("LookupList", None),
|
|
1008
|
+
}
|
|
1009
|
+
for attr, parser in fields.values():
|
|
1010
|
+
setattr(self, attr, None)
|
|
1011
|
+
while lines.peek() is not None:
|
|
1012
|
+
typ = lines.peek()[0].lower()
|
|
1013
|
+
if typ not in fields:
|
|
1014
|
+
log.debug("Skipping %s", lines.peek())
|
|
1015
|
+
next(lines)
|
|
1016
|
+
continue
|
|
1017
|
+
attr, parser = fields[typ]
|
|
1018
|
+
if typ == "lookup":
|
|
1019
|
+
if self.LookupList is None:
|
|
1020
|
+
self.LookupList = ot.LookupList()
|
|
1021
|
+
self.LookupList.Lookup = []
|
|
1022
|
+
_, name, _ = lines.peek()
|
|
1023
|
+
lookup = parseLookup(lines, tableTag, font, lookupMap)
|
|
1024
|
+
if lookupMap is not None:
|
|
1025
|
+
assert name not in lookupMap, "Duplicate lookup name: %s" % name
|
|
1026
|
+
lookupMap[name] = len(self.LookupList.Lookup)
|
|
1027
|
+
else:
|
|
1028
|
+
assert int(name) == len(self.LookupList.Lookup), "%d %d" % (
|
|
1029
|
+
name,
|
|
1030
|
+
len(self.Lookup),
|
|
1031
|
+
)
|
|
1032
|
+
self.LookupList.Lookup.append(lookup)
|
|
1033
|
+
else:
|
|
1034
|
+
assert getattr(self, attr) is None, attr
|
|
1035
|
+
setattr(self, attr, parser(lines))
|
|
1036
|
+
if self.LookupList:
|
|
1037
|
+
self.LookupList.LookupCount = len(self.LookupList.Lookup)
|
|
1038
|
+
if lookupMap is not None:
|
|
1039
|
+
lookupMap.applyDeferredMappings()
|
|
1040
|
+
if os.environ.get(LOOKUP_DEBUG_ENV_VAR):
|
|
1041
|
+
if "Debg" not in font:
|
|
1042
|
+
font["Debg"] = newTable("Debg")
|
|
1043
|
+
font["Debg"].data = {}
|
|
1044
|
+
debug = (
|
|
1045
|
+
font["Debg"]
|
|
1046
|
+
.data.setdefault(LOOKUP_DEBUG_INFO_KEY, {})
|
|
1047
|
+
.setdefault(tableTag, {})
|
|
1048
|
+
)
|
|
1049
|
+
for name, lookup in lookupMap.items():
|
|
1050
|
+
debug[str(lookup)] = ["", name, ""]
|
|
1051
|
+
|
|
1052
|
+
featureMap.applyDeferredMappings()
|
|
1053
|
+
container.table = self
|
|
1054
|
+
return container
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def parseGSUB(lines, font):
|
|
1058
|
+
return parseGSUBGPOS(lines, font, "GSUB")
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
def parseGPOS(lines, font):
|
|
1062
|
+
return parseGSUBGPOS(lines, font, "GPOS")
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
def parseAttachList(lines, font):
|
|
1066
|
+
points = {}
|
|
1067
|
+
with lines.between("attachment list"):
|
|
1068
|
+
for line in lines:
|
|
1069
|
+
glyph = makeGlyph(line[0])
|
|
1070
|
+
assert glyph not in points, glyph
|
|
1071
|
+
points[glyph] = [int(i) for i in line[1:]]
|
|
1072
|
+
return otl.buildAttachList(points, font.getReverseGlyphMap())
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
def parseCaretList(lines, font):
|
|
1076
|
+
carets = {}
|
|
1077
|
+
with lines.between("carets"):
|
|
1078
|
+
for line in lines:
|
|
1079
|
+
glyph = makeGlyph(line[0])
|
|
1080
|
+
assert glyph not in carets, glyph
|
|
1081
|
+
num = int(line[1])
|
|
1082
|
+
thisCarets = [int(i) for i in line[2:]]
|
|
1083
|
+
assert num == len(thisCarets), line
|
|
1084
|
+
carets[glyph] = thisCarets
|
|
1085
|
+
return otl.buildLigCaretList(carets, {}, font.getReverseGlyphMap())
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
def makeMarkFilteringSets(sets, font):
|
|
1089
|
+
self = ot.MarkGlyphSetsDef()
|
|
1090
|
+
self.MarkSetTableFormat = 1
|
|
1091
|
+
self.MarkSetCount = 1 + max(sets.keys())
|
|
1092
|
+
self.Coverage = [None] * self.MarkSetCount
|
|
1093
|
+
for k, v in sorted(sets.items()):
|
|
1094
|
+
self.Coverage[k] = makeCoverage(set(v), font)
|
|
1095
|
+
return self
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def parseMarkFilteringSets(lines, font):
|
|
1099
|
+
sets = {}
|
|
1100
|
+
with lines.between("set definition"):
|
|
1101
|
+
for line in lines:
|
|
1102
|
+
assert len(line) == 2, line
|
|
1103
|
+
glyph = makeGlyph(line[0])
|
|
1104
|
+
# TODO accept set names
|
|
1105
|
+
st = int(line[1])
|
|
1106
|
+
if st not in sets:
|
|
1107
|
+
sets[st] = []
|
|
1108
|
+
sets[st].append(glyph)
|
|
1109
|
+
return makeMarkFilteringSets(sets, font)
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
def parseGDEF(lines, font):
|
|
1113
|
+
container = ttLib.getTableClass("GDEF")()
|
|
1114
|
+
log.debug("Parsing GDEF")
|
|
1115
|
+
self = ot.GDEF()
|
|
1116
|
+
fields = {
|
|
1117
|
+
"class definition begin": (
|
|
1118
|
+
"GlyphClassDef",
|
|
1119
|
+
lambda lines, font: parseClassDef(lines, font, klass=ot.GlyphClassDef),
|
|
1120
|
+
),
|
|
1121
|
+
"attachment list begin": ("AttachList", parseAttachList),
|
|
1122
|
+
"carets begin": ("LigCaretList", parseCaretList),
|
|
1123
|
+
"mark attachment class definition begin": (
|
|
1124
|
+
"MarkAttachClassDef",
|
|
1125
|
+
lambda lines, font: parseClassDef(lines, font, klass=ot.MarkAttachClassDef),
|
|
1126
|
+
),
|
|
1127
|
+
"markfilter set definition begin": ("MarkGlyphSetsDef", parseMarkFilteringSets),
|
|
1128
|
+
}
|
|
1129
|
+
for attr, parser in fields.values():
|
|
1130
|
+
setattr(self, attr, None)
|
|
1131
|
+
while lines.peek() is not None:
|
|
1132
|
+
typ = lines.peek()[0].lower()
|
|
1133
|
+
if typ not in fields:
|
|
1134
|
+
log.debug("Skipping %s", typ)
|
|
1135
|
+
next(lines)
|
|
1136
|
+
continue
|
|
1137
|
+
attr, parser = fields[typ]
|
|
1138
|
+
assert getattr(self, attr) is None, attr
|
|
1139
|
+
setattr(self, attr, parser(lines, font))
|
|
1140
|
+
self.Version = 0x00010000 if self.MarkGlyphSetsDef is None else 0x00010002
|
|
1141
|
+
container.table = self
|
|
1142
|
+
return container
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
def parseCmap(lines, font):
|
|
1146
|
+
container = ttLib.getTableClass("cmap")()
|
|
1147
|
+
log.debug("Parsing cmap")
|
|
1148
|
+
tables = []
|
|
1149
|
+
while lines.peek() is not None:
|
|
1150
|
+
lines.expect("cmap subtable %d" % len(tables))
|
|
1151
|
+
platId, encId, fmt, lang = [
|
|
1152
|
+
parseCmapId(lines, field)
|
|
1153
|
+
for field in ("platformID", "encodingID", "format", "language")
|
|
1154
|
+
]
|
|
1155
|
+
table = cmap_classes[fmt](fmt)
|
|
1156
|
+
table.platformID = platId
|
|
1157
|
+
table.platEncID = encId
|
|
1158
|
+
table.language = lang
|
|
1159
|
+
table.cmap = {}
|
|
1160
|
+
line = next(lines)
|
|
1161
|
+
while line[0] != "end subtable":
|
|
1162
|
+
table.cmap[int(line[0], 16)] = line[1]
|
|
1163
|
+
line = next(lines)
|
|
1164
|
+
tables.append(table)
|
|
1165
|
+
container.tableVersion = 0
|
|
1166
|
+
container.tables = tables
|
|
1167
|
+
return container
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
def parseCmapId(lines, field):
|
|
1171
|
+
line = next(lines)
|
|
1172
|
+
assert field == line[0]
|
|
1173
|
+
return int(line[1])
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
def parseTable(lines, font, tableTag=None):
|
|
1177
|
+
log.debug("Parsing table")
|
|
1178
|
+
line = lines.peeks()
|
|
1179
|
+
tag = None
|
|
1180
|
+
if line[0].split()[0] == "FontDame":
|
|
1181
|
+
tag = line[0].split()[1]
|
|
1182
|
+
elif " ".join(line[0].split()[:3]) == "Font Chef Table":
|
|
1183
|
+
tag = line[0].split()[3]
|
|
1184
|
+
if tag is not None:
|
|
1185
|
+
next(lines)
|
|
1186
|
+
tag = tag.ljust(4)
|
|
1187
|
+
if tableTag is None:
|
|
1188
|
+
tableTag = tag
|
|
1189
|
+
else:
|
|
1190
|
+
assert tableTag == tag, (tableTag, tag)
|
|
1191
|
+
|
|
1192
|
+
assert (
|
|
1193
|
+
tableTag is not None
|
|
1194
|
+
), "Don't know what table to parse and data doesn't specify"
|
|
1195
|
+
|
|
1196
|
+
return {
|
|
1197
|
+
"GSUB": parseGSUB,
|
|
1198
|
+
"GPOS": parseGPOS,
|
|
1199
|
+
"GDEF": parseGDEF,
|
|
1200
|
+
"cmap": parseCmap,
|
|
1201
|
+
}[tableTag](lines, font)
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
class Tokenizer(object):
|
|
1205
|
+
def __init__(self, f):
|
|
1206
|
+
# TODO BytesIO / StringIO as needed? also, figure out whether we work on bytes or unicode
|
|
1207
|
+
lines = iter(f)
|
|
1208
|
+
try:
|
|
1209
|
+
self.filename = f.name
|
|
1210
|
+
except:
|
|
1211
|
+
self.filename = None
|
|
1212
|
+
self.lines = iter(lines)
|
|
1213
|
+
self.line = ""
|
|
1214
|
+
self.lineno = 0
|
|
1215
|
+
self.stoppers = []
|
|
1216
|
+
self.buffer = None
|
|
1217
|
+
|
|
1218
|
+
def __iter__(self):
|
|
1219
|
+
return self
|
|
1220
|
+
|
|
1221
|
+
def _next_line(self):
|
|
1222
|
+
self.lineno += 1
|
|
1223
|
+
line = self.line = next(self.lines)
|
|
1224
|
+
line = [s.strip() for s in line.split("\t")]
|
|
1225
|
+
if len(line) == 1 and not line[0]:
|
|
1226
|
+
del line[0]
|
|
1227
|
+
if line and not line[-1]:
|
|
1228
|
+
log.warning("trailing tab found on line %d: %s" % (self.lineno, self.line))
|
|
1229
|
+
while line and not line[-1]:
|
|
1230
|
+
del line[-1]
|
|
1231
|
+
return line
|
|
1232
|
+
|
|
1233
|
+
def _next_nonempty(self):
|
|
1234
|
+
while True:
|
|
1235
|
+
line = self._next_line()
|
|
1236
|
+
# Skip comments and empty lines
|
|
1237
|
+
if line and line[0] and (line[0][0] != "%" or line[0] == "% subtable"):
|
|
1238
|
+
return line
|
|
1239
|
+
|
|
1240
|
+
def _next_buffered(self):
|
|
1241
|
+
if self.buffer:
|
|
1242
|
+
ret = self.buffer
|
|
1243
|
+
self.buffer = None
|
|
1244
|
+
return ret
|
|
1245
|
+
else:
|
|
1246
|
+
return self._next_nonempty()
|
|
1247
|
+
|
|
1248
|
+
def __next__(self):
|
|
1249
|
+
line = self._next_buffered()
|
|
1250
|
+
if line[0].lower() in self.stoppers:
|
|
1251
|
+
self.buffer = line
|
|
1252
|
+
raise StopIteration
|
|
1253
|
+
return line
|
|
1254
|
+
|
|
1255
|
+
def next(self):
|
|
1256
|
+
return self.__next__()
|
|
1257
|
+
|
|
1258
|
+
def peek(self):
|
|
1259
|
+
if not self.buffer:
|
|
1260
|
+
try:
|
|
1261
|
+
self.buffer = self._next_nonempty()
|
|
1262
|
+
except StopIteration:
|
|
1263
|
+
return None
|
|
1264
|
+
if self.buffer[0].lower() in self.stoppers:
|
|
1265
|
+
return None
|
|
1266
|
+
return self.buffer
|
|
1267
|
+
|
|
1268
|
+
def peeks(self):
|
|
1269
|
+
ret = self.peek()
|
|
1270
|
+
return ret if ret is not None else ("",)
|
|
1271
|
+
|
|
1272
|
+
@contextmanager
|
|
1273
|
+
def between(self, tag):
|
|
1274
|
+
start = tag + " begin"
|
|
1275
|
+
end = tag + " end"
|
|
1276
|
+
self.expectendswith(start)
|
|
1277
|
+
self.stoppers.append(end)
|
|
1278
|
+
yield
|
|
1279
|
+
del self.stoppers[-1]
|
|
1280
|
+
self.expect(tag + " end")
|
|
1281
|
+
|
|
1282
|
+
@contextmanager
|
|
1283
|
+
def until(self, tags):
|
|
1284
|
+
if type(tags) is not tuple:
|
|
1285
|
+
tags = (tags,)
|
|
1286
|
+
self.stoppers.extend(tags)
|
|
1287
|
+
yield
|
|
1288
|
+
del self.stoppers[-len(tags) :]
|
|
1289
|
+
|
|
1290
|
+
def expect(self, s):
|
|
1291
|
+
line = next(self)
|
|
1292
|
+
tag = line[0].lower()
|
|
1293
|
+
assert tag == s, "Expected '%s', got '%s'" % (s, tag)
|
|
1294
|
+
return line
|
|
1295
|
+
|
|
1296
|
+
def expectendswith(self, s):
|
|
1297
|
+
line = next(self)
|
|
1298
|
+
tag = line[0].lower()
|
|
1299
|
+
assert tag.endswith(s), "Expected '*%s', got '%s'" % (s, tag)
|
|
1300
|
+
return line
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
def build(f, font, tableTag=None):
|
|
1304
|
+
"""Convert a Monotype font layout file to an OpenType layout object
|
|
1305
|
+
|
|
1306
|
+
A font object must be passed, but this may be a "dummy" font; it is only
|
|
1307
|
+
used for sorting glyph sets when making coverage tables and to hold the
|
|
1308
|
+
OpenType layout table while it is being built.
|
|
1309
|
+
|
|
1310
|
+
Args:
|
|
1311
|
+
f: A file object.
|
|
1312
|
+
font (TTFont): A font object.
|
|
1313
|
+
tableTag (string): If provided, asserts that the file contains data for the
|
|
1314
|
+
given OpenType table.
|
|
1315
|
+
|
|
1316
|
+
Returns:
|
|
1317
|
+
An object representing the table. (e.g. ``table_G_S_U_B_``)
|
|
1318
|
+
"""
|
|
1319
|
+
lines = Tokenizer(f)
|
|
1320
|
+
return parseTable(lines, font, tableTag=tableTag)
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
def main(args=None, font=None):
|
|
1324
|
+
"""Convert a FontDame OTL file to TTX XML
|
|
1325
|
+
|
|
1326
|
+
Writes XML output to stdout.
|
|
1327
|
+
|
|
1328
|
+
Args:
|
|
1329
|
+
args: Command line arguments (``--font``, ``--table``, input files).
|
|
1330
|
+
"""
|
|
1331
|
+
import sys
|
|
1332
|
+
from fontTools import configLogger
|
|
1333
|
+
from fontTools.misc.testTools import MockFont
|
|
1334
|
+
|
|
1335
|
+
if args is None:
|
|
1336
|
+
args = sys.argv[1:]
|
|
1337
|
+
|
|
1338
|
+
# configure the library logger (for >= WARNING)
|
|
1339
|
+
configLogger()
|
|
1340
|
+
# comment this out to enable debug messages from mtiLib's logger
|
|
1341
|
+
# log.setLevel(logging.DEBUG)
|
|
1342
|
+
|
|
1343
|
+
import argparse
|
|
1344
|
+
|
|
1345
|
+
parser = argparse.ArgumentParser(
|
|
1346
|
+
"fonttools mtiLib",
|
|
1347
|
+
description=main.__doc__,
|
|
1348
|
+
)
|
|
1349
|
+
|
|
1350
|
+
parser.add_argument(
|
|
1351
|
+
"--font",
|
|
1352
|
+
"-f",
|
|
1353
|
+
metavar="FILE",
|
|
1354
|
+
dest="font",
|
|
1355
|
+
help="Input TTF files (used for glyph classes and sorting coverage tables)",
|
|
1356
|
+
)
|
|
1357
|
+
parser.add_argument(
|
|
1358
|
+
"--table",
|
|
1359
|
+
"-t",
|
|
1360
|
+
metavar="TABLE",
|
|
1361
|
+
dest="tableTag",
|
|
1362
|
+
help="Table to fill (sniffed from input file if not provided)",
|
|
1363
|
+
)
|
|
1364
|
+
parser.add_argument(
|
|
1365
|
+
"inputs", metavar="FILE", type=str, nargs="+", help="Input FontDame .txt files"
|
|
1366
|
+
)
|
|
1367
|
+
|
|
1368
|
+
args = parser.parse_args(args)
|
|
1369
|
+
|
|
1370
|
+
if font is None:
|
|
1371
|
+
if args.font:
|
|
1372
|
+
font = ttLib.TTFont(args.font)
|
|
1373
|
+
else:
|
|
1374
|
+
font = MockFont()
|
|
1375
|
+
|
|
1376
|
+
for f in args.inputs:
|
|
1377
|
+
log.debug("Processing %s", f)
|
|
1378
|
+
with open(f, "rt", encoding="utf-8-sig") as f:
|
|
1379
|
+
table = build(f, font, tableTag=args.tableTag)
|
|
1380
|
+
blob = table.compile(font) # Make sure it compiles
|
|
1381
|
+
decompiled = table.__class__()
|
|
1382
|
+
decompiled.decompile(blob, font) # Make sure it decompiles!
|
|
1383
|
+
|
|
1384
|
+
# continue
|
|
1385
|
+
from fontTools.misc import xmlWriter
|
|
1386
|
+
|
|
1387
|
+
tag = table.tableTag
|
|
1388
|
+
writer = xmlWriter.XMLWriter(sys.stdout)
|
|
1389
|
+
writer.begintag(tag)
|
|
1390
|
+
writer.newline()
|
|
1391
|
+
# table.toXML(writer, font)
|
|
1392
|
+
decompiled.toXML(writer, font)
|
|
1393
|
+
writer.endtag(tag)
|
|
1394
|
+
writer.newline()
|
|
1395
|
+
|
|
1396
|
+
|
|
1397
|
+
if __name__ == "__main__":
|
|
1398
|
+
import sys
|
|
1399
|
+
|
|
1400
|
+
sys.exit(main())
|