aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2021-06-19 12:01:13 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2021-06-19 12:01:13 +0000
commitf98087d89a39f513699fade312f31e8f9886b559 (patch)
tree8e925d800e27f7a70c2b6a131b14526bc0f095b5
parent375cb3ca5498f918169cf83acfb5abc6cd950495 (diff)
parentec41266cf300d743bba015596b3b590b16965a76 (diff)
downloadfonttools-android12-mainline-media-release.tar.gz
Change-Id: Ia388a60645cbc10936201e584db2d02cfe12f997
-rw-r--r--.appveyor.yml57
-rw-r--r--.gitattributes11
-rw-r--r--.github/workflows/publish.yml31
-rw-r--r--.github/workflows/test.yml91
-rw-r--r--.gitignore60
-rw-r--r--.pyup.yml4
-rw-r--r--.readthedocs.yml29
-rw-r--r--.travis.yml99
-rwxr-xr-x.travis/after_success.sh16
-rwxr-xr-x.travis/before_install.sh6
-rwxr-xr-x.travis/install.sh30
-rwxr-xr-x.travis/run.sh20
-rw-r--r--Android.bp58
-rw-r--r--CODE_OF_CONDUCT.md76
-rw-r--r--CONTRIBUTING.md26
-rw-r--r--Doc/README.md121
-rw-r--r--Doc/docs-requirements.txt3
-rw-r--r--Doc/source/afmLib.rst9
-rw-r--r--Doc/source/agl.rst9
-rwxr-xr-xDoc/source/assets/img/favicon.icobin0 -> 15406 bytes
-rw-r--r--Doc/source/cffLib.rst7
-rw-r--r--Doc/source/cffLib/index.rst53
-rw-r--r--Doc/source/cffLib/specializer.rst8
-rw-r--r--Doc/source/cffLib/width.rst6
-rw-r--r--Doc/source/colorLib/index.rst11
-rw-r--r--Doc/source/conf.py73
-rw-r--r--Doc/source/cu2qu/index.rst38
-rw-r--r--Doc/source/designspaceLib/index.rst1
-rw-r--r--Doc/source/designspaceLib/readme.rst313
-rw-r--r--Doc/source/developer.rst115
-rw-r--r--Doc/source/encodings.rst14
-rw-r--r--Doc/source/encodings/index.rst21
-rw-r--r--Doc/source/feaLib.rst43
-rw-r--r--Doc/source/feaLib/index.rst40
-rw-r--r--Doc/source/index.rst144
-rw-r--r--Doc/source/merge.rst13
-rw-r--r--Doc/source/misc/arrayTools.rst8
-rw-r--r--Doc/source/misc/bezierTools.rst7
-rw-r--r--Doc/source/misc/classifyTools.rst1
-rw-r--r--Doc/source/misc/cliTools.rst8
-rw-r--r--Doc/source/misc/eexec.rst7
-rw-r--r--Doc/source/misc/encodingTools.rst1
-rw-r--r--Doc/source/misc/etree.rst8
-rw-r--r--Doc/source/misc/filenames.rst7
-rw-r--r--Doc/source/misc/fixedTools.rst7
-rw-r--r--Doc/source/misc/index.rst23
-rw-r--r--Doc/source/misc/intTools.rst6
-rw-r--r--Doc/source/misc/loggingTools.rst6
-rw-r--r--Doc/source/misc/macCreatorType.rst9
-rw-r--r--Doc/source/misc/macRes.rst11
-rw-r--r--Doc/source/misc/plistlib.rst9
-rw-r--r--Doc/source/misc/psCharStrings.rst1
-rw-r--r--Doc/source/misc/psLib.rst8
-rw-r--r--Doc/source/misc/psOperators.rst8
-rw-r--r--Doc/source/misc/py23.rst8
-rw-r--r--Doc/source/misc/sstruct.rst1
-rw-r--r--Doc/source/misc/symfont.rst8
-rw-r--r--Doc/source/misc/testTools.rst1
-rw-r--r--Doc/source/misc/textTools.rst1
-rw-r--r--Doc/source/misc/timeTools.rst1
-rw-r--r--Doc/source/misc/transform.rst1
-rw-r--r--Doc/source/misc/xmlReader.rst1
-rw-r--r--Doc/source/misc/xmlWriter.rst1
-rw-r--r--Doc/source/mtiLib.rst14
-rw-r--r--Doc/source/optional.rst140
-rw-r--r--Doc/source/otlLib/index.rst74
-rw-r--r--Doc/source/pens/areaPen.rst1
-rw-r--r--Doc/source/pens/basePen.rst1
-rw-r--r--Doc/source/pens/boundsPen.rst1
-rw-r--r--Doc/source/pens/cocoaPen.rst8
-rw-r--r--Doc/source/pens/cu2quPen.rst8
-rw-r--r--Doc/source/pens/filterPen.rst1
-rw-r--r--Doc/source/pens/index.rst24
-rw-r--r--Doc/source/pens/momentsPen.rst8
-rw-r--r--Doc/source/pens/perimeterPen.rst1
-rw-r--r--Doc/source/pens/pointInsidePen.rst1
-rw-r--r--Doc/source/pens/pointPen.rst8
-rw-r--r--Doc/source/pens/qtPen.rst8
-rw-r--r--Doc/source/pens/recordingPen.rst1
-rw-r--r--Doc/source/pens/reportLabPen.rst8
-rw-r--r--Doc/source/pens/reverseContourPen.rst8
-rw-r--r--Doc/source/pens/roundingPen.rst8
-rw-r--r--Doc/source/pens/statisticsPen.rst1
-rw-r--r--Doc/source/pens/svgPathPen.rst8
-rw-r--r--Doc/source/pens/t2CharStringPen.rst1
-rw-r--r--Doc/source/pens/teePen.rst1
-rw-r--r--Doc/source/pens/transformPen.rst1
-rw-r--r--Doc/source/pens/ttGlyphPen.rst8
-rw-r--r--Doc/source/pens/wxPen.rst8
-rw-r--r--Doc/source/subset/cff.rst8
-rw-r--r--Doc/source/subset/index.rst (renamed from Doc/source/subset.rst)6
-rw-r--r--Doc/source/svgLib/index.rst8
-rw-r--r--Doc/source/svgLib/path/index.rst13
-rw-r--r--Doc/source/svgLib/path/parser.rst8
-rw-r--r--Doc/source/t1Lib.rst1
-rw-r--r--Doc/source/ttLib/index.rst4
-rw-r--r--Doc/source/ttLib/macUtils.rst1
-rw-r--r--Doc/source/ttLib/sfnt.rst1
-rw-r--r--Doc/source/ttLib/standardGlyphOrder.rst12
-rw-r--r--Doc/source/ttLib/tables.rst359
-rw-r--r--Doc/source/ttLib/ttCollection.rst8
-rw-r--r--Doc/source/ttLib/ttFont.rst9
-rw-r--r--Doc/source/ttLib/woff2.rst1
-rw-r--r--Doc/source/ttx.rst55
-rw-r--r--Doc/source/ufoLib/converters.rst8
-rw-r--r--Doc/source/ufoLib/errors.rst9
-rw-r--r--Doc/source/ufoLib/filenames.rst8
-rw-r--r--Doc/source/ufoLib/glifLib.rst8
-rw-r--r--Doc/source/ufoLib/index.rst22
-rw-r--r--Doc/source/ufoLib/kerning.rst9
-rw-r--r--Doc/source/ufoLib/plistlib.rst9
-rw-r--r--Doc/source/ufoLib/pointPen.rst9
-rw-r--r--Doc/source/ufoLib/pointpen.rst9
-rw-r--r--Doc/source/ufoLib/ufoLib.rst9
-rw-r--r--Doc/source/ufoLib/utils.rst9
-rw-r--r--Doc/source/ufoLib/validators.rst9
-rw-r--r--Doc/source/unicode.rst8
-rw-r--r--Doc/source/unicodedata/Blocks.rst13
-rw-r--r--Doc/source/unicodedata/OTTags.rst17
-rw-r--r--Doc/source/unicodedata/ScriptExtensions.rst12
-rw-r--r--Doc/source/unicodedata/Scripts.rst16
-rw-r--r--Doc/source/unicodedata/index.rst16
-rw-r--r--Doc/source/varLib/builder.rst8
-rw-r--r--Doc/source/varLib/cff.rst8
-rw-r--r--Doc/source/varLib/designspace.rst7
-rw-r--r--Doc/source/varLib/errors.rst8
-rw-r--r--Doc/source/varLib/featureVars.rst8
-rw-r--r--Doc/source/varLib/index.rst105
-rw-r--r--Doc/source/varLib/instancer.rst8
-rw-r--r--Doc/source/varLib/interpolatable.rst1
-rw-r--r--Doc/source/varLib/interpolate_layout.rst1
-rw-r--r--Doc/source/varLib/iup.rst8
-rw-r--r--Doc/source/varLib/merger.rst1
-rw-r--r--Doc/source/varLib/models.rst1
-rw-r--r--Doc/source/varLib/mutator.rst1
-rw-r--r--Doc/source/varLib/mvar.rst10
-rw-r--r--Doc/source/varLib/plot.rst8
-rw-r--r--Doc/source/varLib/varStore.rst8
-rw-r--r--Doc/source/voltLib.rst5
-rw-r--r--Icons/FontToolsIconGreenCircle.pdfbin0 -> 2991 bytes
-rw-r--r--Icons/FontToolsIconGreenCircle.pngbin0 -> 66860 bytes
-rw-r--r--Icons/FontToolsIconGreenSquare.pdfbin0 -> 3236 bytes
-rw-r--r--Icons/FontToolsIconGreenSquare.pngbin0 -> 47926 bytes
-rw-r--r--Icons/FontToolsIconWhiteCircle.pdfbin0 -> 2996 bytes
-rw-r--r--Icons/FontToolsIconWhiteCircle.pngbin0 -> 61225 bytes
-rw-r--r--Icons/FontToolsIconWhiteSquare.pdfbin0 -> 3240 bytes
-rw-r--r--Icons/FontToolsIconWhiteSquare.pngbin0 -> 47913 bytes
-rw-r--r--LICENSE.external211
-rw-r--r--Lib/fontTools/Android.bp25
-rw-r--r--Lib/fontTools/__init__.py4
-rw-r--r--Lib/fontTools/__main__.py7
-rw-r--r--Lib/fontTools/afmLib.py75
-rw-r--r--Lib/fontTools/agl.py4419
-rw-r--r--Lib/fontTools/cffLib/__init__.py282
-rw-r--r--Lib/fontTools/cffLib/specializer.py19
-rw-r--r--Lib/fontTools/cffLib/width.py46
-rw-r--r--Lib/fontTools/colorLib/__init__.py0
-rw-r--r--Lib/fontTools/colorLib/builder.py637
-rw-r--r--Lib/fontTools/colorLib/errors.py3
-rw-r--r--Lib/fontTools/colorLib/geometry.py145
-rw-r--r--Lib/fontTools/colorLib/table_builder.py234
-rw-r--r--Lib/fontTools/colorLib/unbuilder.py79
-rw-r--r--Lib/fontTools/cu2qu/__init__.py15
-rw-r--r--Lib/fontTools/cu2qu/__main__.py6
-rw-r--r--Lib/fontTools/cu2qu/cli.py177
-rw-r--r--Lib/fontTools/cu2qu/cu2qu.py496
-rw-r--r--Lib/fontTools/cu2qu/errors.py76
-rw-r--r--Lib/fontTools/cu2qu/ufo.py324
-rw-r--r--Lib/fontTools/designspaceLib/__init__.py213
-rw-r--r--Lib/fontTools/encodings/MacRoman.py3
-rw-r--r--Lib/fontTools/encodings/StandardEncoding.py3
-rw-r--r--Lib/fontTools/encodings/__init__.py3
-rw-r--r--Lib/fontTools/encodings/codecs.py90
-rw-r--r--Lib/fontTools/feaLib/__main__.py58
-rw-r--r--Lib/fontTools/feaLib/ast.py1173
-rw-r--r--Lib/fontTools/feaLib/builder.py1317
-rw-r--r--Lib/fontTools/feaLib/error.py16
-rw-r--r--Lib/fontTools/feaLib/lexer.py110
-rw-r--r--Lib/fontTools/feaLib/location.py12
-rw-r--r--Lib/fontTools/feaLib/lookupDebugInfo.py11
-rw-r--r--Lib/fontTools/feaLib/parser.py1401
-rw-r--r--Lib/fontTools/fontBuilder.py542
-rw-r--r--Lib/fontTools/help.py34
-rw-r--r--Lib/fontTools/merge.py87
-rw-r--r--Lib/fontTools/misc/__init__.py3
-rw-r--r--Lib/fontTools/misc/arrayTools.py366
-rw-r--r--Lib/fontTools/misc/bezierTools.py922
-rw-r--r--Lib/fontTools/misc/classifyTools.py2
-rw-r--r--Lib/fontTools/misc/cliTools.py24
-rw-r--r--Lib/fontTools/misc/cython.py25
-rw-r--r--Lib/fontTools/misc/dictTools.py2
-rw-r--r--Lib/fontTools/misc/eexec.py66
-rw-r--r--Lib/fontTools/misc/encodingTools.py2
-rw-r--r--Lib/fontTools/misc/etree.py7
-rw-r--r--Lib/fontTools/misc/filenames.py146
-rw-r--r--Lib/fontTools/misc/fixedTools.py248
-rw-r--r--Lib/fontTools/misc/intTools.py22
-rw-r--r--Lib/fontTools/misc/loggingTools.py125
-rw-r--r--Lib/fontTools/misc/macCreatorType.py43
-rw-r--r--Lib/fontTools/misc/macRes.py38
-rw-r--r--Lib/fontTools/misc/plistlib.py545
-rw-r--r--Lib/fontTools/misc/plistlib/__init__.py677
-rw-r--r--Lib/fontTools/misc/plistlib/py.typed0
-rw-r--r--Lib/fontTools/misc/psCharStrings.py34
-rw-r--r--Lib/fontTools/misc/psLib.py25
-rw-r--r--Lib/fontTools/misc/psOperators.py3
-rw-r--r--Lib/fontTools/misc/py23.py647
-rw-r--r--Lib/fontTools/misc/roundTools.py58
-rw-r--r--Lib/fontTools/misc/sstruct.py5
-rw-r--r--Lib/fontTools/misc/symfont.py6
-rw-r--r--Lib/fontTools/misc/testTools.py15
-rw-r--r--Lib/fontTools/misc/textTools.py5
-rw-r--r--Lib/fontTools/misc/timeTools.py10
-rw-r--r--Lib/fontTools/misc/transform.py169
-rw-r--r--Lib/fontTools/misc/vector.py143
-rw-r--r--Lib/fontTools/misc/xmlReader.py2
-rw-r--r--Lib/fontTools/misc/xmlWriter.py9
-rw-r--r--Lib/fontTools/mtiLib/__init__.py55
-rw-r--r--Lib/fontTools/mtiLib/__main__.py2
-rw-r--r--Lib/fontTools/otlLib/builder.py2331
-rw-r--r--Lib/fontTools/otlLib/builder.py.sketch105
-rw-r--r--Lib/fontTools/otlLib/error.py11
-rw-r--r--Lib/fontTools/otlLib/maxContextCalc.py49
-rw-r--r--Lib/fontTools/pens/__init__.py3
-rw-r--r--Lib/fontTools/pens/areaPen.py2
-rw-r--r--Lib/fontTools/pens/basePen.py38
-rw-r--r--Lib/fontTools/pens/boundsPen.py2
-rw-r--r--Lib/fontTools/pens/cocoaPen.py2
-rw-r--r--Lib/fontTools/pens/cu2quPen.py257
-rw-r--r--Lib/fontTools/pens/filterPen.py45
-rw-r--r--Lib/fontTools/pens/hashPointPen.py77
-rw-r--r--Lib/fontTools/pens/momentsPen.py11
-rw-r--r--Lib/fontTools/pens/perimeterPen.py2
-rw-r--r--Lib/fontTools/pens/pointInsidePen.py2
-rw-r--r--Lib/fontTools/pens/pointPen.py62
-rw-r--r--Lib/fontTools/pens/qtPen.py2
-rw-r--r--Lib/fontTools/pens/quartzPen.py45
-rw-r--r--Lib/fontTools/pens/recordingPen.py56
-rw-r--r--Lib/fontTools/pens/reportLabPen.py2
-rw-r--r--Lib/fontTools/pens/reverseContourPen.py2
-rw-r--r--Lib/fontTools/pens/roundingPen.py112
-rw-r--r--Lib/fontTools/pens/statisticsPen.py2
-rw-r--r--Lib/fontTools/pens/svgPathPen.py2
-rw-r--r--Lib/fontTools/pens/t2CharStringPen.py32
-rw-r--r--Lib/fontTools/pens/teePen.py2
-rw-r--r--Lib/fontTools/pens/transformPen.py49
-rw-r--r--Lib/fontTools/pens/ttGlyphPen.py60
-rw-r--r--Lib/fontTools/pens/wxPen.py2
-rw-r--r--Lib/fontTools/subset/__init__.py258
-rw-r--r--Lib/fontTools/subset/__main__.py3
-rw-r--r--Lib/fontTools/subset/cff.py2
-rw-r--r--Lib/fontTools/svgLib/__init__.py3
-rw-r--r--Lib/fontTools/svgLib/path/__init__.py4
-rw-r--r--Lib/fontTools/svgLib/path/arc.py5
-rw-r--r--Lib/fontTools/svgLib/path/parser.py72
-rw-r--r--Lib/fontTools/t1Lib/__init__.py3
-rw-r--r--Lib/fontTools/ttLib/__init__.py2
-rw-r--r--Lib/fontTools/ttLib/macUtils.py5
-rw-r--r--Lib/fontTools/ttLib/removeOverlaps.py192
-rw-r--r--Lib/fontTools/ttLib/sfnt.py60
-rw-r--r--Lib/fontTools/ttLib/standardGlyphOrder.py3
-rw-r--r--Lib/fontTools/ttLib/tables/B_A_S_E_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py2
-rw-r--r--Lib/fontTools/ttLib/tables/C_B_D_T_.py3
-rw-r--r--Lib/fontTools/ttLib/tables/C_B_L_C_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/C_F_F_.py3
-rw-r--r--Lib/fontTools/ttLib/tables/C_F_F__2.py4
-rw-r--r--Lib/fontTools/ttLib/tables/C_O_L_R_.py217
-rw-r--r--Lib/fontTools/ttLib/tables/C_P_A_L_.py55
-rw-r--r--Lib/fontTools/ttLib/tables/D_S_I_G_.py3
-rw-r--r--Lib/fontTools/ttLib/tables/D__e_b_g.py17
-rw-r--r--Lib/fontTools/ttLib/tables/DefaultTable.py3
-rw-r--r--Lib/fontTools/ttLib/tables/E_B_D_T_.py3
-rw-r--r--Lib/fontTools/ttLib/tables/E_B_L_C_.py7
-rw-r--r--Lib/fontTools/ttLib/tables/F_F_T_M_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/F__e_a_t.py5
-rw-r--r--Lib/fontTools/ttLib/tables/G_D_E_F_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/G_M_A_P_.py5
-rw-r--r--Lib/fontTools/ttLib/tables/G_P_K_G_.py15
-rw-r--r--Lib/fontTools/ttLib/tables/G_P_O_S_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/G_S_U_B_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/G__l_a_t.py8
-rw-r--r--Lib/fontTools/ttLib/tables/G__l_o_c.py10
-rw-r--r--Lib/fontTools/ttLib/tables/H_V_A_R_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/J_S_T_F_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/L_T_S_H_.py6
-rw-r--r--Lib/fontTools/ttLib/tables/M_A_T_H_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/M_E_T_A_.py15
-rw-r--r--Lib/fontTools/ttLib/tables/M_V_A_R_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/O_S_2f_2.py49
-rw-r--r--Lib/fontTools/ttLib/tables/S_I_N_G_.py3
-rw-r--r--Lib/fontTools/ttLib/tables/S_T_A_T_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/S_V_G_.py5
-rw-r--r--Lib/fontTools/ttLib/tables/S__i_l_f.py18
-rw-r--r--Lib/fontTools/ttLib/tables/S__i_l_l.py9
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_B_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_C_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_D_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_J_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_P_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_S_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_V_.py3
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I__0.py2
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I__1.py5
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I__2.py2
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I__3.py2
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I__5.py6
-rw-r--r--Lib/fontTools/ttLib/tables/T_T_F_A_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/TupleVariation.py39
-rw-r--r--Lib/fontTools/ttLib/tables/V_D_M_X_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/V_O_R_G_.py6
-rw-r--r--Lib/fontTools/ttLib/tables/V_V_A_R_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/__init__.py5
-rw-r--r--Lib/fontTools/ttLib/tables/_a_n_k_r.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_a_v_a_r.py29
-rw-r--r--Lib/fontTools/ttLib/tables/_b_s_l_n.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_c_i_d_g.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_c_m_a_p.py21
-rw-r--r--Lib/fontTools/ttLib/tables/_c_v_a_r.py4
-rw-r--r--Lib/fontTools/ttLib/tables/_c_v_t.py6
-rw-r--r--Lib/fontTools/ttLib/tables/_f_e_a_t.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_f_p_g_m.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_f_v_a_r.py33
-rw-r--r--Lib/fontTools/ttLib/tables/_g_a_s_p.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_g_c_i_d.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_g_l_y_f.py143
-rw-r--r--Lib/fontTools/ttLib/tables/_g_v_a_r.py11
-rw-r--r--Lib/fontTools/ttLib/tables/_h_d_m_x.py9
-rw-r--r--Lib/fontTools/ttLib/tables/_h_e_a_d.py35
-rw-r--r--Lib/fontTools/ttLib/tables/_h_h_e_a.py26
-rw-r--r--Lib/fontTools/ttLib/tables/_h_m_t_x.py6
-rw-r--r--Lib/fontTools/ttLib/tables/_k_e_r_n.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_l_c_a_r.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_l_o_c_a.py6
-rw-r--r--Lib/fontTools/ttLib/tables/_l_t_a_g.py3
-rw-r--r--Lib/fontTools/ttLib/tables/_m_a_x_p.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_m_e_t_a.py9
-rw-r--r--Lib/fontTools/ttLib/tables/_m_o_r_t.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_m_o_r_x.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_n_a_m_e.py160
-rw-r--r--Lib/fontTools/ttLib/tables/_o_p_b_d.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_p_o_s_t.py11
-rw-r--r--Lib/fontTools/ttLib/tables/_p_r_e_p.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_p_r_o_p.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_s_b_i_x.py5
-rw-r--r--Lib/fontTools/ttLib/tables/_t_r_a_k.py23
-rw-r--r--Lib/fontTools/ttLib/tables/_v_h_e_a.py13
-rw-r--r--Lib/fontTools/ttLib/tables/_v_m_t_x.py2
-rw-r--r--Lib/fontTools/ttLib/tables/asciiTable.py3
-rw-r--r--Lib/fontTools/ttLib/tables/otBase.py140
-rw-r--r--Lib/fontTools/ttLib/tables/otConverters.py310
-rwxr-xr-xLib/fontTools/ttLib/tables/otData.py286
-rw-r--r--Lib/fontTools/ttLib/tables/otTables.py316
-rw-r--r--Lib/fontTools/ttLib/tables/sbixGlyph.py2
-rw-r--r--Lib/fontTools/ttLib/tables/sbixStrike.py6
-rw-r--r--Lib/fontTools/ttLib/tables/ttProgram.py6
-rw-r--r--Lib/fontTools/ttLib/ttCollection.py15
-rw-r--r--Lib/fontTools/ttLib/ttFont.py165
-rw-r--r--Lib/fontTools/ttLib/woff2.py82
-rw-r--r--Lib/fontTools/ttx.py4
-rwxr-xr-xLib/fontTools/ufoLib/__init__.py475
-rw-r--r--Lib/fontTools/ufoLib/converters.py13
-rw-r--r--Lib/fontTools/ufoLib/errors.py9
-rw-r--r--Lib/fontTools/ufoLib/filenames.py13
-rwxr-xr-xLib/fontTools/ufoLib/glifLib.py354
-rw-r--r--Lib/fontTools/ufoLib/kerning.py1
-rw-r--r--Lib/fontTools/ufoLib/plistlib.py7
-rw-r--r--Lib/fontTools/ufoLib/utils.py77
-rw-r--r--Lib/fontTools/ufoLib/validators.py90
-rw-r--r--Lib/fontTools/unicode.py8
-rw-r--r--Lib/fontTools/unicodedata/Blocks.py50
-rw-r--r--Lib/fontTools/unicodedata/ScriptExtensions.py46
-rw-r--r--Lib/fontTools/unicodedata/Scripts.py390
-rw-r--r--Lib/fontTools/unicodedata/__init__.py32
-rw-r--r--Lib/fontTools/varLib/__init__.py245
-rw-r--r--Lib/fontTools/varLib/__main__.py3
-rw-r--r--Lib/fontTools/varLib/builder.py1
-rw-r--r--Lib/fontTools/varLib/cff.py118
-rw-r--r--Lib/fontTools/varLib/errors.py190
-rw-r--r--Lib/fontTools/varLib/featureVars.py68
-rw-r--r--Lib/fontTools/varLib/instancer/__init__.py (renamed from Lib/fontTools/varLib/instancer.py)811
-rw-r--r--Lib/fontTools/varLib/instancer/__main__.py5
-rw-r--r--Lib/fontTools/varLib/instancer/names.py379
-rw-r--r--Lib/fontTools/varLib/interpolatable.py495
-rw-r--r--Lib/fontTools/varLib/interpolate_layout.py39
-rw-r--r--Lib/fontTools/varLib/iup.py5
-rw-r--r--Lib/fontTools/varLib/merger.py171
-rw-r--r--Lib/fontTools/varLib/models.py149
-rw-r--r--Lib/fontTools/varLib/mutator.py21
-rw-r--r--Lib/fontTools/varLib/mvar.py4
-rw-r--r--Lib/fontTools/varLib/plot.py8
-rw-r--r--Lib/fontTools/varLib/varStore.py30
-rw-r--r--Lib/fontTools/voltLib/ast.py235
-rw-r--r--Lib/fontTools/voltLib/error.py2
-rw-r--r--Lib/fontTools/voltLib/lexer.py2
-rw-r--r--Lib/fontTools/voltLib/parser.py33
-rw-r--r--Lib/fonttools.egg-info/PKG-INFO1823
-rw-r--r--Lib/fonttools.egg-info/SOURCES.txt1820
-rw-r--r--Lib/fonttools.egg-info/dependency_links.txt1
-rw-r--r--Lib/fonttools.egg-info/entry_points.txt6
-rw-r--r--Lib/fonttools.egg-info/requires.txt76
-rw-r--r--Lib/fonttools.egg-info/top_level.txt1
-rw-r--r--MANIFEST.in11
-rw-r--r--METADATA14
-rw-r--r--Makefile7
-rwxr-xr-xMetaTools/buildTableList.py12
-rwxr-xr-xMetaTools/buildUCD.py4
-rwxr-xr-xMetaTools/roundTrip.py8
-rw-r--r--NEWS.rst550
-rw-r--r--PKG-INFO1823
-rw-r--r--README.rst218
-rwxr-xr-xSnippets/cmap-format.py4
-rwxr-xr-xSnippets/decompose-ttf.py53
-rw-r--r--Snippets/dump_woff_metadata.py1
-rw-r--r--Snippets/fix-dflt-langsys.py2
-rwxr-xr-xSnippets/fontTools1
-rwxr-xr-xSnippets/interpolate.py4
-rwxr-xr-xSnippets/layout-features.py4
-rw-r--r--Snippets/merge_woff_metadata.py1
-rw-r--r--Snippets/name-viewer.ipynb117
-rwxr-xr-xSnippets/otf2ttf.py13
-rwxr-xr-xSnippets/rename-fonts.py3
-rwxr-xr-xSnippets/subset-fpgm.py4
-rwxr-xr-xSnippets/svg2glif.py5
-rw-r--r--Tests/afmLib/afmLib_test.py2
-rw-r--r--Tests/agl_test.py18
-rw-r--r--Tests/cffLib/cffLib_test.py16
-rwxr-xr-xTests/cffLib/data/LinLibertine_RBI.otfbin0 -> 363740 bytes
-rw-r--r--Tests/cffLib/data/TestCFF2Widths.ttx2
-rw-r--r--Tests/cffLib/specializer_test.py1
-rw-r--r--Tests/colorLib/__init__.py0
-rw-r--r--Tests/colorLib/builder_test.py1662
-rw-r--r--Tests/colorLib/table_builder_test.py15
-rw-r--r--Tests/colorLib/unbuilder_test.py210
-rw-r--r--Tests/cu2qu/cli_test.py86
-rw-r--r--Tests/cu2qu/cu2qu_test.py178
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/fontinfo.plist205
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/A_.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/B_.glif51
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/C_.glif37
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/D_.glif35
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/E_.glif31
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/F_.glif25
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/G_.glif41
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/H_.glif25
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/I_.glif13
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/J_.glif25
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/K_.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/L_.glif19
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/M_.glif30
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/N_.glif19
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/O_.glif39
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/P_.glif29
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Q_.glif45
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/R_.glif37
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/S_.glif47
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/T_.glif19
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/U_.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/V_.glif21
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/W_.glif37
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/X_.glif21
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Y_.glif18
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Z_.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/a.glif56
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/b.glif46
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/c.glif37
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/contents.plist112
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/d.glif46
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/e.glif41
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/f.glif31
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/g.glif58
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/h.glif31
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/i.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/j.glif39
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/k.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/l.glif13
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/m.glif50
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/n.glif32
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/o.glif39
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/p.glif46
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/q.glif46
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/r.glif30
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/s.glif47
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/space.glif38
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/t.glif31
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/u.glif32
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/v.glif21
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/w.glif37
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/x.glif21
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/y.glif33
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/z.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/layercontents.plist10
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/lib.plist62
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Bold.ufo/metainfo.plist10
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/fontinfo.plist214
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/A_.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/B_.glif51
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/C_.glif37
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/D_.glif35
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/E_.glif31
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/F_.glif25
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/G_.glif41
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/H_.glif25
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/I_.glif13
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/J_.glif25
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/K_.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/L_.glif19
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/M_.glif30
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/N_.glif19
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/O_.glif39
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/P_.glif29
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Q_.glif45
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/R_.glif37
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/S_.glif47
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/T_.glif19
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/U_.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/V_.glif21
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/W_.glif37
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/X_.glif21
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Y_.glif18
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Z_.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/a.glif56
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/b.glif46
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/c.glif37
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/contents.plist112
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/d.glif46
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/e.glif41
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/f.glif31
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/g.glif58
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/h.glif31
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/i.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/j.glif39
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/k.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/l.glif13
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/m.glif50
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/n.glif32
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/o.glif39
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/p.glif46
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/q.glif46
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/r.glif30
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/s.glif47
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/space.glif5
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/t.glif31
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/u.glif32
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/v.glif21
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/w.glif37
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/x.glif21
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/y.glif33
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/z.glif27
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/layercontents.plist10
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/lib.plist62
-rw-r--r--Tests/cu2qu/data/RobotoSubset-Regular.ufo/metainfo.plist10
-rw-r--r--Tests/cu2qu/data/curves.json1
-rw-r--r--Tests/cu2qu/ufo_test.py285
-rw-r--r--Tests/designspaceLib/data/test.designspace4
-rw-r--r--Tests/designspaceLib/designspace_test.py81
-rw-r--r--Tests/encodings/codecs_test.py10
-rw-r--r--Tests/feaLib/STAT2.fea4
-rw-r--r--Tests/feaLib/ast_test.py8
-rw-r--r--Tests/feaLib/builder_test.py322
-rw-r--r--Tests/feaLib/data/AlternateChained.fea3
-rw-r--r--Tests/feaLib/data/AlternateChained.ttx78
-rw-r--r--Tests/feaLib/data/ChainPosSubtable.fea4
-rw-r--r--Tests/feaLib/data/ChainPosSubtable.ttx36
-rw-r--r--Tests/feaLib/data/ChainSubstSubtable.fea8
-rw-r--r--Tests/feaLib/data/ChainSubstSubtable.ttx20
-rw-r--r--Tests/feaLib/data/GPOS_2.ttx1
-rw-r--r--Tests/feaLib/data/GPOS_2b.ttx5
-rw-r--r--Tests/feaLib/data/GPOS_4.fea10
-rw-r--r--Tests/feaLib/data/GPOS_5.fea29
-rw-r--r--Tests/feaLib/data/GPOS_6.fea7
-rw-r--r--Tests/feaLib/data/GPOS_8.ttx68
-rw-r--r--Tests/feaLib/data/GSUB_5_formats.fea20
-rw-r--r--Tests/feaLib/data/GSUB_5_formats.ttx197
-rw-r--r--Tests/feaLib/data/GSUB_6.fea4
-rw-r--r--Tests/feaLib/data/GSUB_6.ttx52
-rw-r--r--Tests/feaLib/data/GSUB_6_formats.fea20
-rw-r--r--Tests/feaLib/data/GSUB_6_formats.ttx256
-rw-r--r--Tests/feaLib/data/GSUB_8.fea2
-rw-r--r--Tests/feaLib/data/GSUB_error.fea13
-rw-r--r--Tests/feaLib/data/LigatureCaretByIndex.fea4
-rw-r--r--Tests/feaLib/data/LigatureCaretByIndex.ttx5
-rw-r--r--Tests/feaLib/data/LigatureCaretByPos.fea4
-rw-r--r--Tests/feaLib/data/LigatureCaretByPos.ttx5
-rw-r--r--Tests/feaLib/data/MultipleLookupsPerGlyph.fea11
-rw-r--r--Tests/feaLib/data/MultipleLookupsPerGlyph.ttx76
-rw-r--r--Tests/feaLib/data/MultipleLookupsPerGlyph2.fea11
-rw-r--r--Tests/feaLib/data/MultipleLookupsPerGlyph2.ttx84
-rw-r--r--Tests/feaLib/data/PairPosSubtable.ttx5
-rw-r--r--Tests/feaLib/data/STAT_bad.fea96
-rw-r--r--Tests/feaLib/data/STAT_test.fea109
-rw-r--r--Tests/feaLib/data/STAT_test.ttx228
-rw-r--r--Tests/feaLib/data/STAT_test_elidedFallbackNameID.fea84
-rw-r--r--Tests/feaLib/data/STAT_test_elidedFallbackNameID.ttx225
-rw-r--r--Tests/feaLib/data/SubstSubtable.fea9
-rw-r--r--Tests/feaLib/data/SubstSubtable.ttx116
-rw-r--r--Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx69
-rw-r--r--Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.ttx69
-rw-r--r--Tests/feaLib/data/aalt_chain_contextual_subst.fea20
-rw-r--r--Tests/feaLib/data/aalt_chain_contextual_subst.ttx139
-rw-r--r--Tests/feaLib/data/bug453.fea6
-rw-r--r--Tests/feaLib/data/bug506.ttx20
-rw-r--r--Tests/feaLib/data/bug509.ttx30
-rw-r--r--Tests/feaLib/data/bug512.ttx97
-rw-r--r--Tests/feaLib/data/bug514.fea4
-rw-r--r--Tests/feaLib/data/bug633.ttx1
-rw-r--r--Tests/feaLib/data/cid_range.fea6
-rw-r--r--Tests/feaLib/data/cid_range.ttx244
-rw-r--r--Tests/feaLib/data/delete_glyph.fea3
-rw-r--r--Tests/feaLib/data/delete_glyph.ttx43
-rw-r--r--Tests/feaLib/data/include/test.fea1
-rw-r--r--Tests/feaLib/data/include/test.ufo/features.fea1
-rw-r--r--Tests/feaLib/data/language_required.fea2
-rw-r--r--Tests/feaLib/data/lookup-debug.ttx80
-rw-r--r--Tests/feaLib/data/lookupflag.fea62
-rw-r--r--Tests/feaLib/data/lookupflag.ttx206
-rw-r--r--Tests/feaLib/data/size2.ttx4
-rw-r--r--Tests/feaLib/data/spec4h1.fea2
-rw-r--r--Tests/feaLib/data/spec5f_ii_2.fea2
-rw-r--r--Tests/feaLib/data/spec5f_ii_3.fea2
-rw-r--r--Tests/feaLib/data/spec5f_ii_3.ttx212
-rw-r--r--Tests/feaLib/data/spec5f_ii_4.fea6
-rw-r--r--Tests/feaLib/data/spec5fi2.ttx4
-rw-r--r--Tests/feaLib/data/spec5fi3.fea2
-rw-r--r--Tests/feaLib/data/spec6b_ii.ttx1
-rw-r--r--Tests/feaLib/data/spec6d2.fea10
-rw-r--r--Tests/feaLib/data/spec6e.fea9
-rw-r--r--Tests/feaLib/data/spec6f.fea3
-rw-r--r--Tests/feaLib/data/spec6h_ii.fea6
-rw-r--r--Tests/feaLib/data/spec6h_ii.ttx24
-rw-r--r--Tests/feaLib/data/spec6h_iii_3d.ttx20
-rw-r--r--Tests/feaLib/data/spec8a.fea2
-rw-r--r--Tests/feaLib/data/test.fea1
-rw-r--r--Tests/feaLib/error_test.py5
-rw-r--r--Tests/feaLib/lexer_test.py32
-rw-r--r--Tests/feaLib/parser_test.py193
-rw-r--r--Tests/fontBuilder/data/test.otf.ttx2
-rw-r--r--Tests/fontBuilder/data/test.ttf.ttx2
-rw-r--r--Tests/fontBuilder/data/test_var.otf.ttx26
-rw-r--r--Tests/fontBuilder/data/test_var.ttf.ttx204
-rw-r--r--Tests/fontBuilder/fontBuilder_test.py46
-rw-r--r--Tests/merge_test.py55
-rw-r--r--Tests/misc/arrayTools_test.py5
-rw-r--r--Tests/misc/bezierTools_test.py23
-rw-r--r--Tests/misc/classifyTools_test.py2
-rw-r--r--Tests/misc/eexec_test.py2
-rw-r--r--Tests/misc/encodingTools_test.py4
-rw-r--r--Tests/misc/etree_test.py1
-rw-r--r--Tests/misc/filenames_test.py1
-rw-r--r--Tests/misc/fixedTools_test.py59
-rw-r--r--Tests/misc/loggingTools_test.py3
-rw-r--r--Tests/misc/macRes_test.py3
-rw-r--r--Tests/misc/plistlib_test.py27
-rw-r--r--Tests/misc/psCharStrings_test.py92
-rw-r--r--Tests/misc/py23_test.py33
-rw-r--r--Tests/misc/testTools_test.py4
-rw-r--r--Tests/misc/textTools_test.py2
-rw-r--r--Tests/misc/timeTools_test.py21
-rw-r--r--Tests/misc/transform_test.py2
-rw-r--r--Tests/misc/vector_test.py71
-rw-r--r--Tests/misc/xmlReader_test.py6
-rw-r--r--Tests/misc/xmlWriter_test.py4
-rw-r--r--Tests/mtiLib/data/featurename-backward.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/featurename-forward.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/lookupnames-backward.ttx.GSUB8
-rw-r--r--Tests/mtiLib/data/lookupnames-forward.ttx.GSUB8
-rw-r--r--Tests/mtiLib/data/mixed-toplevels.ttx.GSUB8
-rw-r--r--Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS4
-rw-r--r--Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB4
-rw-r--r--Tests/mtiLib/data/mti/chainedclass.ttx.GSUB8
-rw-r--r--Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB8
-rw-r--r--Tests/mtiLib/data/mti/gdefattach.ttx.GDEF2
-rw-r--r--Tests/mtiLib/data/mti/gdefclasses.ttx.GDEF2
-rw-r--r--Tests/mtiLib/data/mti/gdefligcaret.ttx.GDEF2
-rw-r--r--Tests/mtiLib/data/mti/gdefmarkattach.ttx.GDEF2
-rw-r--r--Tests/mtiLib/data/mti/gdefmarkfilter.ttx.GDEF6
-rw-r--r--Tests/mtiLib/data/mti/gposcursive.ttx.GPOS2
-rw-r--r--Tests/mtiLib/data/mti/gposkernset.ttx.GPOS8
-rw-r--r--Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS4
-rw-r--r--Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS6
-rw-r--r--Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS2
-rw-r--r--Tests/mtiLib/data/mti/gpossingle.ttx.GPOS2
-rw-r--r--Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/mti/gsubligature.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB14
-rw-r--r--Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS4
-rw-r--r--Tests/mtiLib/mti_test.py4
-rw-r--r--Tests/otlLib/__init__.py0
-rw-r--r--Tests/otlLib/builder_test.py384
-rw-r--r--Tests/otlLib/data/gpos_91.ttx2
-rw-r--r--Tests/otlLib/data/gsub_51.ttx12
-rw-r--r--Tests/otlLib/data/gsub_52.ttx14
-rw-r--r--Tests/otlLib/data/gsub_71.ttx2
-rw-r--r--Tests/otlLib/maxContextCalc_test.py2
-rw-r--r--Tests/otlLib/mock_builder_test.py86
-rw-r--r--Tests/pens/__init__.py13
-rw-r--r--Tests/pens/areaPen_test.py2
-rw-r--r--Tests/pens/basePen_test.py5
-rw-r--r--Tests/pens/boundsPen_test.py2
-rw-r--r--Tests/pens/cocoaPen_test.py58
-rw-r--r--Tests/pens/cu2quPen_test.py390
-rwxr-xr-xTests/pens/data/cubic/A_.glif58
-rwxr-xr-xTests/pens/data/cubic/A_acute.glif9
-rwxr-xr-xTests/pens/data/cubic/E_acute.glif56
-rw-r--r--Tests/pens/data/cubic/a.glif80
-rwxr-xr-xTests/pens/data/cubic/acute.glif13
-rw-r--r--Tests/pens/data/cubic/contents.plist16
-rw-r--r--Tests/pens/data/quadratic/A_.glif74
-rw-r--r--Tests/pens/data/quadratic/E_acute.glif64
-rw-r--r--Tests/pens/data/quadratic/a.glif93
-rwxr-xr-xTests/pens/data/quadratic/acute.glif13
-rw-r--r--Tests/pens/data/quadratic/contents.plist14
-rw-r--r--Tests/pens/hashPointPen_test.py138
-rw-r--r--Tests/pens/perimeterPen_test.py2
-rw-r--r--Tests/pens/pointInsidePen_test.py11
-rw-r--r--Tests/pens/pointPen_test.py90
-rw-r--r--Tests/pens/quartzPen_test.py78
-rw-r--r--Tests/pens/recordingPen_test.py53
-rw-r--r--Tests/pens/reverseContourPen_test.py20
-rw-r--r--Tests/pens/t2CharStringPen_test.py10
-rw-r--r--Tests/pens/ttGlyphPen_test.py55
-rw-r--r--Tests/pens/utils.py281
-rw-r--r--Tests/subset/data/CmapSubsetTest.subset.ttx14
-rw-r--r--Tests/subset/data/CmapSubsetTest.ttx225
-rw-r--r--Tests/subset/data/GPOS_PairPos_Format2_ClassDef1_useClass0.subset.ttx62
-rw-r--r--Tests/subset/data/GPOS_PairPos_Format2_ClassDef2_useClass0.subset.ttx17
-rw-r--r--Tests/subset/data/GPOS_PairPos_Format2_PR_2221.ttx322
-rw-r--r--Tests/subset/data/Lobster.subset.ttx8
-rw-r--r--Tests/subset/data/TestContextSubstFormat3.ttx604
-rw-r--r--Tests/subset/data/expect_keep_math.ttx12
-rw-r--r--Tests/subset/data/expect_layout_scripts.ttx129
-rw-r--r--Tests/subset/data/layout_scripts.ttx997
-rw-r--r--Tests/subset/data/test_cntrmask_CFF.ttx10
-rw-r--r--Tests/subset/subset_test.py491
-rw-r--r--Tests/svgLib/path/parser_test.py86
-rw-r--r--Tests/svgLib/path/path_test.py4
-rw-r--r--Tests/svgLib/path/shapes_test.py3
-rw-r--r--Tests/t1Lib/t1Lib_test.py2
-rw-r--r--Tests/ttLib/data/woff2_overlap_offcurve_in.ttx306
-rw-r--r--Tests/ttLib/sfnt_test.py57
-rw-r--r--Tests/ttLib/tables/C_B_L_C_test.py80
-rw-r--r--Tests/ttLib/tables/C_F_F__2_test.py8
-rw-r--r--Tests/ttLib/tables/C_F_F_test.py6
-rw-r--r--Tests/ttLib/tables/C_O_L_R_test.py512
-rw-r--r--Tests/ttLib/tables/C_P_A_L_test.py19
-rw-r--r--Tests/ttLib/tables/M_V_A_R_test.py2
-rw-r--r--Tests/ttLib/tables/O_S_2f_2_test.py1
-rw-r--r--Tests/ttLib/tables/S_T_A_T_test.py6
-rw-r--r--Tests/ttLib/tables/T_S_I__0_test.py3
-rw-r--r--Tests/ttLib/tables/T_S_I__1_test.py7
-rw-r--r--Tests/ttLib/tables/TupleVariation_test.py58
-rw-r--r--Tests/ttLib/tables/_a_n_k_r_test.py2
-rw-r--r--Tests/ttLib/tables/_a_v_a_r_test.py28
-rw-r--r--Tests/ttLib/tables/_b_s_l_n_test.py2
-rw-r--r--Tests/ttLib/tables/_c_i_d_g_test.py3
-rw-r--r--Tests/ttLib/tables/_c_m_a_p_test.py2
-rw-r--r--Tests/ttLib/tables/_c_v_a_r_test.py22
-rw-r--r--Tests/ttLib/tables/_f_p_g_m_test.py2
-rw-r--r--Tests/ttLib/tables/_f_v_a_r_test.py18
-rw-r--r--Tests/ttLib/tables/_g_c_i_d_test.py3
-rw-r--r--Tests/ttLib/tables/_g_l_y_f_test.py370
-rw-r--r--Tests/ttLib/tables/_g_v_a_r_test.py46
-rw-r--r--Tests/ttLib/tables/_h_h_e_a_test.py14
-rw-r--r--Tests/ttLib/tables/_h_m_t_x_test.py3
-rw-r--r--Tests/ttLib/tables/_k_e_r_n_test.py2
-rw-r--r--Tests/ttLib/tables/_l_c_a_r_test.py2
-rw-r--r--Tests/ttLib/tables/_l_t_a_g_test.py3
-rw-r--r--Tests/ttLib/tables/_m_e_t_a_test.py16
-rw-r--r--Tests/ttLib/tables/_m_o_r_t_test.py2
-rw-r--r--Tests/ttLib/tables/_m_o_r_x_test.py4
-rw-r--r--Tests/ttLib/tables/_n_a_m_e_test.py162
-rw-r--r--Tests/ttLib/tables/_o_p_b_d_test.py2
-rw-r--r--Tests/ttLib/tables/_p_r_o_p_test.py2
-rw-r--r--Tests/ttLib/tables/_t_r_a_k_test.py2
-rw-r--r--Tests/ttLib/tables/_v_h_e_a_test.py2
-rw-r--r--Tests/ttLib/tables/_v_m_t_x_test.py3
-rw-r--r--Tests/ttLib/tables/data/C_F_F__2.binbin2312 -> 2312 bytes
-rw-r--r--Tests/ttLib/tables/data/C_F_F__2.ttx4
-rw-r--r--Tests/ttLib/tables/data/NotoColorEmoji.subset.index_format_3.ttx705
-rw-r--r--Tests/ttLib/tables/data/aots/classdef1_font1.ttx.GSUB10
-rw-r--r--Tests/ttLib/tables/data/aots/classdef1_font2.ttx.GSUB10
-rw-r--r--Tests/ttLib/tables/data/aots/classdef1_font3.ttx.GSUB10
-rw-r--r--Tests/ttLib/tables/data/aots/classdef1_font4.ttx.GSUB10
-rw-r--r--Tests/ttLib/tables/data/aots/classdef2_font1.ttx.GSUB10
-rw-r--r--Tests/ttLib/tables/data/aots/classdef2_font2.ttx.GSUB10
-rw-r--r--Tests/ttLib/tables/data/aots/classdef2_font3.ttx.GSUB10
-rw-r--r--Tests/ttLib/tables/data/aots/classdef2_font4.ttx.GSUB10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos1_1_simple_f1.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos1_1_simple_f2.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos1_1_simple_f3.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos1_1_simple_f4.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos1_2_font1.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_1_font6.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_1_font7.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f1.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f2.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_1_simple_f1.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_2_font1.ttx.GPOS6
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GPOS8
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GPOS8
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_2_font4.ttx.GPOS6
-rw-r--r--Tests/ttLib/tables/data/aots/gpos2_2_font5.ttx.GPOS6
-rw-r--r--Tests/ttLib/tables/data/aots/gpos3_font1.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GDEF4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GPOS6
-rw-r--r--Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GPOS6
-rw-r--r--Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos7_1_font1.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos9_font1.ttx.GPOS2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos9_font2.ttx.GPOS4
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GPOS18
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GPOS14
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GPOS14
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GPOS24
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GPOS22
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GPOS20
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GPOS14
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GPOS14
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GPOS10
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GPOS12
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GPOS14
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GPOS16
-rw-r--r--Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/gsub1_1_modulo_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub1_1_simple_f1.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/gsub1_2_simple_f1.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/gsub2_1_multiple_sequences_f1.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub2_1_simple_f1.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/gsub3_1_multiple_f1.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub3_1_simple_f1.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f1.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f2.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligsets_f1.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub4_1_simple_f1.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub7_font1.ttx.GSUB2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub7_font2.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GSUB20
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GSUB16
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GSUB16
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GSUB26
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GSUB24
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GSUB22
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GSUB16
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GSUB16
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GSUB12
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GSUB14
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GSUB16
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GSUB18
-rw-r--r--Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GDEF4
-rw-r--r--Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GDEF4
-rw-r--r--Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GDEF2
-rw-r--r--Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GSUB4
-rw-r--r--Tests/ttLib/tables/otBase_test.py2
-rw-r--r--Tests/ttLib/tables/otConverters_test.py4
-rw-r--r--Tests/ttLib/tables/otTables_test.py131
-rw-r--r--Tests/ttLib/tables/tables_test.py8
-rw-r--r--Tests/ttLib/tables/ttProgram_test.py6
-rw-r--r--Tests/ttLib/ttFont_test.py48
-rw-r--r--Tests/ttLib/woff2_test.py44
-rw-r--r--Tests/ttx/ttx_test.py7
-rw-r--r--Tests/ufoLib/GLIF1_test.py12
-rw-r--r--Tests/ufoLib/GLIF2_test.py12
-rw-r--r--Tests/ufoLib/UFO1_test.py4
-rw-r--r--Tests/ufoLib/UFO2_test.py4
-rw-r--r--Tests/ufoLib/UFO3_test.py59
-rw-r--r--Tests/ufoLib/UFOConversion_test.py32
-rw-r--r--Tests/ufoLib/UFOZ_test.py8
-rw-r--r--Tests/ufoLib/filenames_test.py1
-rw-r--r--Tests/ufoLib/glifLib_test.py226
-rwxr-xr-xTests/ufoLib/testSupport.py21
-rw-r--r--Tests/ufoLib/testdata/TestFont1 (UFO3).ufozbin0 -> 10577 bytes
-rw-r--r--Tests/ufoLib/testdata/UFO3-Read Data.ufo/groups.plist28
-rw-r--r--Tests/ufoLib/ufoLib_test.py98
-rw-r--r--Tests/unicodedata_test.py300
-rw-r--r--Tests/varLib/builder_test.py1
-rw-r--r--Tests/varLib/data/FeatureVars.designspace2
-rw-r--r--Tests/varLib/data/FeatureVarsCustomTag.designspace77
-rw-r--r--Tests/varLib/data/FeatureVarsWholeRange.designspace34
-rw-r--r--Tests/varLib/data/FeatureVarsWholeRangeEmpty.designspace33
-rw-r--r--Tests/varLib/data/IncompatibleArrays.designspace22
-rw-r--r--Tests/varLib/data/IncompatibleFeatures.designspace22
-rw-r--r--Tests/varLib/data/IncompatibleLookupTypes.designspace22
-rw-r--r--Tests/varLib/data/SingleMaster.designspace13
-rw-r--r--Tests/varLib/data/TestBASE.designspace36
-rw-r--r--Tests/varLib/data/TestCFF2Input.designspace93
-rw-r--r--Tests/varLib/data/VarLibLocationTest.designspace26
-rw-r--r--Tests/varLib/data/master_base_test/TestBASE.0.ttx700
-rw-r--r--Tests/varLib/data/master_base_test/TestBASE.900.ttx700
-rw-r--r--Tests/varLib/data/master_cff2/TestCFF2_Black.ttx2
-rw-r--r--Tests/varLib/data/master_cff2/TestCFF2_ExtraLight.ttx2
-rw-r--r--Tests/varLib/data/master_cff2/TestCFF2_Regular.ttx2
-rw-r--r--Tests/varLib/data/master_cff2_input/TestCFF2_Black.ttx510
-rw-r--r--Tests/varLib/data/master_cff2_input/TestCFF2_ExtraLight.ttx510
-rw-r--r--Tests/varLib/data/master_cff2_input/TestCFF2_Regular.ttx508
-rw-r--r--Tests/varLib/data/master_incompatible_arrays/IncompatibleArrays-Bold.ttx612
-rw-r--r--Tests/varLib/data/master_incompatible_arrays/IncompatibleArrays-Regular.ttx626
-rw-r--r--Tests/varLib/data/master_incompatible_features/IncompatibleFeatures-Bold.ttx578
-rw-r--r--Tests/varLib/data/master_incompatible_features/IncompatibleFeatures-Regular.ttx626
-rw-r--r--Tests/varLib/data/master_incompatible_lookup_types/IncompatibleLookupTypes-Bold.ttx622
-rw-r--r--Tests/varLib/data/master_incompatible_lookup_types/IncompatibleLookupTypes-Regular.ttx626
-rw-r--r--Tests/varLib/data/master_kerning_merging/0.ttx8
-rw-r--r--Tests/varLib/data/master_kerning_merging/1.ttx8
-rw-r--r--Tests/varLib/data/master_kerning_merging/2.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_otf/TestFamily2-Master0.ttx14
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Bold.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Regular.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master0.ttx2
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master1.ttx2
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master2.ttx2
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master3.ttx2
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master4.ttx2
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily2-Master0.ttx14
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Bold.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Condensed.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedBold.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedLight.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedSemiBold.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Light.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Regular.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-SemiBold.ttx8
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Italic15.ttx16
-rw-r--r--Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Regular.ttx16
-rw-r--r--Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx4
-rw-r--r--Tests/varLib/data/master_vpal_test/master_vpal_test_0.ttx10
-rw-r--r--Tests/varLib/data/master_vpal_test/master_vpal_test_1.ttx6
-rw-r--r--Tests/varLib/data/master_vvar_cff2/TestVVAR.0.ttx4
-rw-r--r--Tests/varLib/data/master_vvar_cff2/TestVVAR.1.ttx4
-rw-r--r--Tests/varLib/data/test_results/Build.ttx1008
-rw-r--r--Tests/varLib/data/test_results/BuildMain.ttx1008
-rw-r--r--Tests/varLib/data/test_results/BuildTestCFF2.ttx12
-rw-r--r--Tests/varLib/data/test_results/FeatureVars.ttx8
-rw-r--r--Tests/varLib/data/test_results/FeatureVarsCustomTag.ttx180
-rw-r--r--Tests/varLib/data/test_results/FeatureVarsWholeRange.ttx75
-rw-r--r--Tests/varLib/data/test_results/FeatureVars_rclt.ttx249
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayout.ttx14
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff.ttx2
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff2.ttx2
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_same.ttx2
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff.ttx6
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff2.ttx6
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_same.ttx6
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff.ttx2
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff2.ttx2
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_same.ttx2
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_diff.ttx2
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_same.ttx2
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_diff.ttx4
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_same.ttx4
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_diff.ttx4
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_same.ttx4
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_diff.ttx4
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_same.ttx4
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx (renamed from Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_diff.ttx)52
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx (renamed from Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_same.ttx)52
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutMain.ttx2
-rw-r--r--Tests/varLib/data/test_results/InterpolateTestCFF2VF.ttx2
-rw-r--r--Tests/varLib/data/test_results/Mutator.ttx2
-rw-r--r--Tests/varLib/data/test_results/SingleMaster.ttx103
-rw-r--r--Tests/varLib/data/test_results/SparseMasters.ttx46
-rw-r--r--Tests/varLib/data/test_results/TestBASE.ttx477
-rw-r--r--Tests/varLib/data/test_results/TestNonMarkingCFF2.ttx2
-rw-r--r--Tests/varLib/data/test_results/TestSparseCFF2VF.ttx8
-rw-r--r--Tests/varLib/data/test_results/test_vpal.ttx12
-rw-r--r--Tests/varLib/featureVars_test.py2
-rw-r--r--Tests/varLib/instancer/conftest.py13
-rw-r--r--Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx (renamed from Tests/varLib/data/PartialInstancerTest-VF.ttx)12
-rw-r--r--Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx (renamed from Tests/varLib/data/PartialInstancerTest2-VF.ttx)151
-rw-r--r--Tests/varLib/instancer/data/PartialInstancerTest3-VF.ttx439
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx (renamed from Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx)82
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,62.5.ttx (renamed from Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-100,62.5.ttx)82
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx (renamed from Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx)77
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,62.5.ttx (renamed from Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-400,62.5.ttx)83
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx (renamed from Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx)82
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,62.5.ttx (renamed from Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-900,62.5.ttx)82
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-400-no-overlap-flags.ttx305
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-400-no-overlaps.ttx343
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-700-no-overlaps.ttx367
-rw-r--r--Tests/varLib/instancer/instancer_test.py (renamed from Tests/varLib/instancer_test.py)679
-rw-r--r--Tests/varLib/instancer/names_test.py322
-rw-r--r--Tests/varLib/interpolatable_test.py2
-rw-r--r--Tests/varLib/interpolate_layout_test.py14
-rw-r--r--Tests/varLib/models_test.py6
-rw-r--r--Tests/varLib/mutator_test.py2
-rw-r--r--Tests/varLib/varLib_test.py280
-rw-r--r--Tests/voltLib/lexer_test.py2
-rw-r--r--Tests/voltLib/parser_test.py345
-rw-r--r--dev-requirements.txt1
-rwxr-xr-xfonttools3
-rw-r--r--mypy.ini21
-rw-r--r--requirements.txt20
-rwxr-xr-xrun-tests.sh28
-rw-r--r--setup.cfg13
-rwxr-xr-xsetup.py145
-rw-r--r--tox.ini22
1258 files changed, 58092 insertions, 16637 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index 9701df65..00000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-environment:
- matrix:
- - JOB: "2.7 32-bit"
- PYTHON_HOME: "C:\\Python27"
-
- - JOB: "3.7 64-bit"
- PYTHON_HOME: "C:\\Python37-x64"
-
-branches:
- only:
- - master
- # We want to build wip/* branches since these are not usually used for PRs
- - /^wip\/.*$/
- # We want to build version tags as well.
- - /^\d+\.\d+.*$/
-
-install:
- # If there is a newer build queued for the same PR, cancel this one.
- # The AppVeyor 'rollout builds' option is supposed to serve the same
- # purpose but it is problematic because it tends to cancel builds pushed
- # directly to master instead of just PR builds (or the converse).
- # credits: JuliaLang developers.
- - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
- https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
- Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
- throw "There are newer queued builds for this pull request, failing early." }
-
- # Prepend Python to the PATH of this build
- - "SET PATH=%PYTHON_HOME%;%PYTHON_HOME%\\Scripts;%PATH%"
-
- # check that we have the expected version and architecture for Python
- - "python --version"
- - "python -c \"import struct; print(struct.calcsize('P') * 8)\""
-
- # upgrade pip and setuptools to avoid out-of-date warnings
- - "python -m pip install --disable-pip-version-check --user --upgrade pip setuptools virtualenv"
-
- # install the dependencies to run the tests
- - "python -m pip install tox"
-
-build: false
-
-test_script:
- # run tests with the current 'python' in %PATH%, and measure test coverage
- - "tox -e py-cov"
-
-after_test:
- # upload test coverage to Codecov.io
- - "tox -e codecov"
-
-notifications:
- - provider: Email
- to:
- - fonttools-dev@googlegroups.com
- on_build_success: false
- on_build_failure: true
- on_build_status_changed: true
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..b3b2b25e
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,11 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.py text
+
+# Font files are binary (so that autocrlf doesn't muck with them)
+*.lwfn binary
+*.pfa binary
+*.pfb binary
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 00000000..ec6abe20
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,31 @@
+# This workflows will upload a Python Package using Twine when a tag is created
+# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
+
+name: Upload Python Package
+
+on:
+ push:
+ # Sequence of patterns matched against refs/tags
+ tags:
+ - '*.*.*' # e.g. 1.0.0 or 20.15.10
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.x'
+ - name: Install dependencies
+ run: |
+ pip install setuptools wheel twine
+ - name: Build and publish
+ env:
+ TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+ run: |
+ python setup.py sdist bdist_wheel
+ twine upload dist/*
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..89d668d0
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,91 @@
+name: Test
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ # https://github.community/t/github-actions-does-not-respect-skip-ci/17325/8
+ if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ python-version: "3.x"
+ - name: Install packages
+ run: pip install tox
+ - name: Run Tox
+ run: tox -e mypy,package_readme
+
+ test:
+ runs-on: ${{ matrix.platform }}
+ if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
+ strategy:
+ matrix:
+ python-version: [3.6, 3.7, 3.8, 3.9]
+ platform: [ubuntu-latest, macos-latest, windows-latest]
+ exclude: # Only test on the oldest and latest supported stable Python on macOS and Windows.
+ - platform: macos-latest
+ python-version: 3.7
+ - platform: macos-latest
+ python-version: 3.8
+ - platform: windows-latest
+ python-version: 3.7
+ - platform: windows-latest
+ python-version: 3.8
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install packages
+ run: pip install tox coverage
+ - name: Run Tox
+ run: tox -e py-cov
+ - name: Run Tox without lxml
+ run: tox -e py-cov-nolxml
+ - name: Produce coverage files
+ run: |
+ coverage combine
+ coverage xml
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v1
+ with:
+ file: coverage.xml
+ flags: unittests
+ name: codecov-umbrella
+ fail_ci_if_error: true
+
+ test-cython:
+ runs-on: ubuntu-latest
+ if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ python-version: "3.x"
+ - name: Install packages
+ run: pip install tox
+ - name: Run Tox
+ run: tox -e py-cy-nolxml
+
+ test-pypy3:
+ runs-on: ubuntu-latest
+ if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python pypy3
+ uses: actions/setup-python@v2
+ with:
+ python-version: "pypy3"
+ - name: Install packages
+ run: pip install tox
+ - name: Run Tox
+ run: tox -e pypy3-nolxml
diff --git a/.gitignore b/.gitignore
index 4fca027d..eba633ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,59 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+dist/
+.eggs/
+*.egg-info/
+*.egg
MANIFEST
-build
-dist
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+coverage.xml
+*.cover
+.pytest_cache/
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# emacs backup files
+*~
+
+# OSX Finder
+.DS_Store
+
+# VSCode
+.vscode
+
+# Cython sources (e.g. cu2qu)
+Lib/**/*.c
+
+# Ctags
+tags
diff --git a/.pyup.yml b/.pyup.yml
new file mode 100644
index 00000000..6ea20650
--- /dev/null
+++ b/.pyup.yml
@@ -0,0 +1,4 @@
+# autogenerated pyup.io config file
+# see https://pyup.io/docs/configuration/ for all available options
+
+schedule: every week
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 00000000..928d6587
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,29 @@
+# .readthedocs.yml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+build:
+ image: latest
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+ configuration: Doc/source/conf.py
+ fail_on_warning: false
+
+# Optionally build your docs in additional formats such as PDF and ePub
+formats:
+ - htmlzip
+ - epub
+
+# Optionally set the version of Python and requirements required to build your docs
+python:
+ version: 3.8
+ install:
+ - requirements: Doc/docs-requirements.txt
+ - method: pip
+ path: .
+ extra_requirements:
+ - all
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 66f80fc0..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,99 +0,0 @@
-language: python
-python: 3.5
-
-env:
- global:
- - TWINE_USERNAME="anthrotype"
- - secure: PJuCmlDuwnojiw3QuDhfNAaU4f/yeJcEcRzJAudA66bwZK7hvxV7Tiy9A17Bm6yO0HbJmmyjsIr8h2e7/PyY6QCaV8RqcMDkQ0UraU16pRsihp0giVXJoWscj2sCP4cNDOBVwSaGAX8yZ2OONc5srESywghzcy8xmgw6O+XFqx4=
-
-branches:
- only:
- - master
- # We want to build wip/* branches since these are not usually used for PRs
- - /^wip\/.*$/
- # We want to build version tags as well.
- - /^\d+\.\d+.*$/
-
-matrix:
- fast_finish: true
- exclude:
- # Exclude the default Python 3.5 build
- - python: 3.5
- include:
- - python: 2.7
- env: TOXENV=py27-cov
- - python: 3.5
- env: TOXENV=py35-cov
- - python: 3.6
- env:
- - TOXENV=py36-cov,package_readme
- - BUILD_DIST=true
- - python: 3.7
- env: TOXENV=py37-cov
- # required to run python3.7 on Travis CI
- # https://github.com/travis-ci/travis-ci/issues/9815
- dist: xenial
- - python: pypy2.7-6.0
- # disable coverage.py on pypy because of performance problems
- env: TOXENV=pypy
- dist: xenial
- - language: generic
- os: osx
- env: TOXENV=py27-cov
- - language: generic
- os: osx
- env:
- - TOXENV=py3-cov
- - HOMEBREW_NO_AUTO_UPDATE=1
- - env:
- - TOXENV=py27
- - PYENV_VERSION='2.7.6'
- - PYENV_VERSION_STRING='Python 2.7.6'
- - PYENV_ROOT=$HOME/.travis-pyenv
- - TRAVIS_PYENV_VERSION='0.4.0'
- allow_failures:
- # We use fast_finish + allow_failures because OSX builds take forever
- # https://blog.travis-ci.com/2013-11-27-fast-finishing-builds
- - language: generic
- os: osx
- env: TOXENV=py27-cov
- - language: generic
- os: osx
- env:
- - TOXENV=py3-cov
- - HOMEBREW_NO_AUTO_UPDATE=1
-
-cache:
- - pip
- - directories:
- - $HOME/.pyenv_cache
-
-before_install:
- - source ./.travis/before_install.sh
-
-install:
- - ./.travis/install.sh
-
-script:
- - ./.travis/run.sh
-
-after_success:
- - ./.travis/after_success.sh
-
-notifications:
- irc: "irc.freenode.org##fonts"
- email: fonttools-dev@googlegroups.com
-
-deploy:
- # deploy to Github Releases on tags
- - provider: releases
- api_key:
- secure: KEcWhJxMcnKay7wmWJCpg2W5GWHTQ+LaRbqGM11IKGcQuEOFxWuG7W1xjGpVdKPj/MQ+cG0b9hGUFpls1hwseOA1HANMv4xjCgYkuvT1OdpX/KOcZ7gfe/qaovzVxHyP9xwohnHSJMb790t37fmDfFUSROx3iEexIX09LLoDjO8=
- skip_cleanup: true
- file_glob: true
- file: "dist/*"
- on:
- tags: true
- repo: fonttools/fonttools
- all_branches: true
- condition: "$BUILD_DIST == true"
diff --git a/.travis/after_success.sh b/.travis/after_success.sh
deleted file mode 100755
index 07bcab5e..00000000
--- a/.travis/after_success.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-
-set -e
-set -x
-
-if [ "$TRAVIS_OS_NAME" == "osx" ]; then
- source .venv/bin/activate
-fi
-
-# upload coverage data to Codecov.io
-[[ ${TOXENV} == *"-cov"* ]] && tox -e codecov
-
-# if tagged commit, create distribution packages and deploy to PyPI
-if [ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_REPO_SLUG" == "fonttools/fonttools" ] && [ "$BUILD_DIST" == true ]; then
- tox -e pypi
-fi
diff --git a/.travis/before_install.sh b/.travis/before_install.sh
deleted file mode 100755
index 8cc4edba..00000000
--- a/.travis/before_install.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-if [[ -n "$PYENV_VERSION" ]]; then
- wget https://github.com/praekeltfoundation/travis-pyenv/releases/download/${TRAVIS_PYENV_VERSION}/setup-pyenv.sh
- source setup-pyenv.sh
-fi
diff --git a/.travis/install.sh b/.travis/install.sh
deleted file mode 100755
index f2a0717f..00000000
--- a/.travis/install.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-
-set -e
-set -x
-
-ci_requirements="pip setuptools tox"
-
-if [ "$TRAVIS_OS_NAME" == "osx" ]; then
- if [[ ${TOXENV} == *"py27"* ]]; then
- # install pip on the system python
- curl -O https://bootstrap.pypa.io/get-pip.py
- python get-pip.py --user
- python -m pip install --user virtualenv
- python -m virtualenv .venv/
- elif [[ ${TOXENV} == *"py3"* ]]; then
- # install current python3 with homebrew
- # NOTE: the formula is now named just "python"
- brew install python
- command -v python3
- python3 --version
- python3 -m pip install virtualenv
- python3 -m virtualenv .venv/
- else
- echo "unsupported $TOXENV: "${TOXENV}
- exit 1
- fi
- source .venv/bin/activate
-fi
-
-python -m pip install $ci_requirements
diff --git a/.travis/run.sh b/.travis/run.sh
deleted file mode 100755
index ffb0ef79..00000000
--- a/.travis/run.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-set -e
-set -x
-
-if [ "$TRAVIS_OS_NAME" == "osx" ]; then
- source .venv/bin/activate
-fi
-
-tox
-
-# re-run all the XML-related tests, this time without lxml but using the
-# built-in ElementTree library.
-if [ -z "$TOXENV" ]; then
- TOXENV="py-nolxml"
-else
- # strip additional tox envs after the comma, add -nolxml factor
- TOXENV="${TOXENV%,*}-nolxml"
-fi
-tox -e $TOXENV -- Tests/ufoLib Tests/misc/etree_test.py Tests/misc/plistlib_test.py
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 00000000..0dc5289f
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,58 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// *** THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS. PLEASE
+// CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE
+// DEPENDING ON IT IN YOUR PROJECT. ***
+package {
+ default_applicable_licenses: ["external_fonttools_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+//
+// large-scale-change included anything that looked like it might be a license
+// text as a license_text. e.g. LICENSE, NOTICE, COPYING etc.
+//
+// Please consider removing redundant or irrelevant files from 'license_text:'.
+// See: http://go/android-license-faq
+license {
+ name: "external_fonttools_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ "SPDX-license-identifier-BSD",
+ "SPDX-license-identifier-MIT",
+ "SPDX-license-identifier-OFL", // by exception only
+ "SPDX-license-identifier-Unicode-DFS",
+ "legacy_unencumbered",
+ ],
+ license_text: [
+ "LICENSE",
+ "LICENSE.external",
+ "NOTICE",
+ ],
+}
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..30f4976b
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at <cosimo@anthrotype.com>. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..ea116c41
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,26 @@
+## How to Contribute
+
+FontTools development is on-going in an active community of developers, that includes professional developers employed at major software corporations and at type foundries, as well as hobbyists.
+
+The project is run on Github, in the typical free/libre/open-source software way.
+If you are unfamiliar with that, check out [opensource.guide](https://opensource.guide) and [producingoss.com](http://producingoss.com).
+
+We use Github's Issue Tracker to report, discuss and track bugs, map out future improvements, set priorities, and self-assign issues.
+If you find a bug, have an idea for a new feature, then please [create a new issue](https://github.com/fonttools/fonttools/issues) and we'll be happy to work with you on it!
+
+If you have a question or want to discuss usage from an end-user perspective, there is a mailing list at [groups.google.com/d/forum/fonttools](https://groups.google.com/d/forum/fonttools) mailing list.
+
+If you would like to speak to someone directly, you can also email the project lead, Behdad Esfahbod, privately at <behdad@behdad.org>
+
+If you make a pull request, you (or the organization that owns your copyrights) should be listed in the [README](https://github.com/fonttools/fonttools#copyrights).
+
+(There is also a development [groups.google.com/d/forum/fonttools-dev](https://groups.google.com/d/forum/fonttools-dev) mailing list for Continuous Integration notifications.)
+
+## Code reviews
+
+All submissions, including submissions by project members, go through a review process using GitHub Pull Requests.
+Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on making Pull Requests.
+
+## Code of Conduct
+
+This project has a [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
diff --git a/Doc/README.md b/Doc/README.md
new file mode 100644
index 00000000..26641b72
--- /dev/null
+++ b/Doc/README.md
@@ -0,0 +1,121 @@
+# fontTools Documentation
+
+The fontTools project documentation updates continuously on Read the Docs as the project source changes.
+
+The documentation is hosted at https://fonttools.readthedocs.io/.
+
+## Contents
+
+- [How to Build Local Documentation](#how-to-build-local-documentation)
+- [Contributing to the fontTools Documentation](#contributing-to-the-documentation)
+- [Documentation License](#documentation-license)
+
+## How to Build Local Documentation
+
+### Install Dependencies
+
+You must have a Python 3 interpreter and the `pip` Python package manager installed on your system to build the fontTools documentation.
+
+Pull the fontTools project source files, create a Python virtual environment, and then install fontTools and the documentation build dependencies by executing the following commands in the root of the fontTools source repository:
+
+```
+$ pip install -e . [all]
+$ pip install -r Doc/docs-requirements.txt
+```
+
+### Build Documentation
+
+**With `make`**: execute the following command in the root of the repository:
+
+```
+$ make docs
+```
+
+**Without `make`**: execute the following command in the **`Doc` directory**:
+
+```
+$ sphinx-build -b html source build
+```
+
+Open the `Doc/build/html/index.html` file in your browser to view the documentation home page.
+
+## Contributing to the Documentation
+
+We highly encourage contributions! Please follow the instructions below to improve the documentation.
+
+### Python Docstring Style
+
+We recommend the use of Python docstrings that follow [the Google Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#381-docstrings). Our documentation build approach parses appropriately formatted docstrings into formatted documentation files.
+
+#### Function Documentation Example
+
+```python
+def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
+ """Fetches rows from a Bigtable.
+
+ Retrieves rows pertaining to the given keys from the Table instance
+ represented by big_table. Silly things may happen if
+ other_silly_variable is not None.
+
+ Args:
+ big_table: An open Bigtable Table instance.
+ keys: A sequence of strings representing the key of each table row
+ to fetch.
+ other_silly_variable: Another optional variable, that has a much
+ longer name than the other args, and which does nothing.
+
+ Returns:
+ A dict mapping keys to the corresponding table row data
+ fetched. Each row is represented as a tuple of strings. For
+ example:
+
+ {'Serak': ('Rigel VII', 'Preparer'),
+ 'Zim': ('Irk', 'Invader'),
+ 'Lrrr': ('Omicron Persei 8', 'Emperor')}
+
+ If a key from the keys argument is missing from the dictionary,
+ then that row was not found in the table.
+
+ Raises:
+ IOError: An error occurred accessing the bigtable.Table object.
+ """
+```
+*Source: [Google Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md) (CC BY-SA 3.0)*
+
+#### Class Documentation Example
+
+```python
+class SampleClass(object):
+ """Summary of class here.
+
+ Longer class information....
+ Longer class information....
+
+ Attributes:
+ likes_spam: A boolean indicating if we like SPAM or not.
+ eggs: An integer count of the eggs we have laid.
+ """
+
+ def __init__(self, likes_spam=False):
+ """Inits SampleClass with blah."""
+ self.likes_spam = likes_spam
+ self.eggs = 0
+
+ def public_method(self):
+ """Performs operation blah."""
+```
+*Source: [Google Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md) (CC BY-SA 3.0)*
+
+### Build Local Documentation and Review Your Changes
+
+Build a local set of HTML documentation files with the instructions above and review your changes.
+
+### Submit a Pull Request
+
+Submit a Github pull request with your proposed improvements to the documentation.
+
+Thanks for your contribution!
+
+## Documentation License
+
+The fontTools documentation is released under a [CC BY-SA 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
diff --git a/Doc/docs-requirements.txt b/Doc/docs-requirements.txt
new file mode 100644
index 00000000..c62c8d1e
--- /dev/null
+++ b/Doc/docs-requirements.txt
@@ -0,0 +1,3 @@
+sphinx==3.3.1
+sphinx_rtd_theme==0.5.0
+reportlab==3.5.55
diff --git a/Doc/source/afmLib.rst b/Doc/source/afmLib.rst
index f56d3c1b..ab9f3567 100644
--- a/Doc/source/afmLib.rst
+++ b/Doc/source/afmLib.rst
@@ -1,7 +1,8 @@
-######
-afmLib
-######
+###########################################
+afmLib: Read/write Adobe Font Metrics files
+###########################################
.. automodule:: fontTools.afmLib
+
+.. autoclass:: fontTools.afmLib.AFM
:members:
- :undoc-members:
diff --git a/Doc/source/agl.rst b/Doc/source/agl.rst
index 0ecf14d0..6e89857f 100644
--- a/Doc/source/agl.rst
+++ b/Doc/source/agl.rst
@@ -1,7 +1,6 @@
-###
-agl
-###
+######################################
+agl: Interface to the Adobe Glyph List
+######################################
.. automodule:: fontTools.agl
- :members:
- :undoc-members:
+ :members: toUnicode, UV2AGL, AGL2UV
diff --git a/Doc/source/assets/img/favicon.ico b/Doc/source/assets/img/favicon.ico
new file mode 100755
index 00000000..f55a275d
--- /dev/null
+++ b/Doc/source/assets/img/favicon.ico
Binary files differ
diff --git a/Doc/source/cffLib.rst b/Doc/source/cffLib.rst
deleted file mode 100644
index 364824f4..00000000
--- a/Doc/source/cffLib.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-######
-cffLib
-######
-
-.. automodule:: fontTools.cffLib
- :members:
- :undoc-members:
diff --git a/Doc/source/cffLib/index.rst b/Doc/source/cffLib/index.rst
new file mode 100644
index 00000000..281a0b12
--- /dev/null
+++ b/Doc/source/cffLib/index.rst
@@ -0,0 +1,53 @@
+##################################
+cffLib: read/write Adobe CFF fonts
+##################################
+
+.. automodule:: fontTools.cffLib
+
+This package also contains two modules for manipulating CFF format glyphs:
+
+.. toctree::
+ :maxdepth: 1
+
+ specializer
+ width
+
+.. autoclass:: fontTools.cffLib.CFFFontSet
+ :inherited-members:
+ :members:
+
+.. autoclass:: fontTools.cffLib.TopDict
+ :members:
+
+.. autoclass:: fontTools.cffLib.CharStrings
+ :members:
+
+.. autoclass:: fontTools.cffLib.Index
+ :members:
+
+.. autoclass:: fontTools.cffLib.GlobalSubrsIndex
+ :members:
+
+.. autoclass:: fontTools.cffLib.TopDictIndex
+ :members:
+
+.. autoclass:: fontTools.cffLib.CFFWriter
+ :members:
+
+.. autoclass:: fontTools.cffLib.IndexCompiler
+ :members:
+
+.. autoclass:: fontTools.cffLib.TopDictIndexCompiler
+ :members:
+
+.. autoclass:: fontTools.cffLib.FDArrayIndexCompiler
+ :members:
+
+.. autoclass:: fontTools.cffLib.GlobalSubrsCompiler
+ :members:
+
+.. autoclass:: fontTools.cffLib.SubrsCompiler
+ :members:
+
+.. autoclass:: fontTools.cffLib.CharStringsCompiler
+ :members:
diff --git a/Doc/source/cffLib/specializer.rst b/Doc/source/cffLib/specializer.rst
new file mode 100644
index 00000000..016a8962
--- /dev/null
+++ b/Doc/source/cffLib/specializer.rst
@@ -0,0 +1,8 @@
+##############################################################
+specializer: T2CharString operator specializer and generalizer
+##############################################################
+
+.. automodule:: fontTools.cffLib.specializer
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/cffLib/width.rst b/Doc/source/cffLib/width.rst
new file mode 100644
index 00000000..68944da8
--- /dev/null
+++ b/Doc/source/cffLib/width.rst
@@ -0,0 +1,6 @@
+#########################################
+width: T2CharString glyph width optimizer
+#########################################
+
+.. automodule:: fontTools.cffLib.width
+ :members: optimizeWidths, optimizeWidthsBruteforce
diff --git a/Doc/source/colorLib/index.rst b/Doc/source/colorLib/index.rst
new file mode 100644
index 00000000..d4eb9f83
--- /dev/null
+++ b/Doc/source/colorLib/index.rst
@@ -0,0 +1,11 @@
+#####################################################
+colorLib.builder: Build COLR/CPAL tables from scratch
+#####################################################
+
+.. automodule:: fontTools.colorLib.builder
+ :members: buildCPAL, buildCOLR, populateCOLRv0
+
+.. autoclass:: fontTools.colorLib.builder.ColorPaletteType
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/conf.py b/Doc/source/conf.py
index a3b2be2a..82a5d579 100644
--- a/Doc/source/conf.py
+++ b/Doc/source/conf.py
@@ -25,40 +25,43 @@
# If your documentation needs a minimal Sphinx version, state it here.
#
-needs_sphinx = '1.3'
+needs_sphinx = "1.3"
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
+extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon", "sphinx.ext.coverage"]
-autodoc_mock_imports = ['gtk']
+autodoc_mock_imports = ["gtk"]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
+source_suffix = ".rst"
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = u'fontTools'
-copyright = u'2017, Just van Rossum, Behdad Esfahbod et al.'
-author = u'Just van Rossum, Behdad Esfahbod et al.'
+project = u"fontTools"
+copyright = u"2020, Just van Rossum, Behdad Esfahbod, and the fontTools Authors. CC BY-SA 4.0"
+author = u"Just van Rossum, Behdad Esfahbod, and the fontTools Authors"
+
+# HTML page title
+html_title = "fontTools Documentation"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = u'3.10'
+version = u"4.0"
# The full version, including alpha/beta/rc tags.
-release = u'3.10'
+release = u"4.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -73,10 +76,11 @@ language = None
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+# pygments_style = "sphinx" (the default sphinx docs style on RTD)
+pygments_style = "default"
# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
+todo_include_todos = True
# -- Options for HTML output ----------------------------------------------
@@ -84,24 +88,29 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
-html_theme = 'classic'
+html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
-# html_theme_options = {}
+html_theme_options = {"display_version": False}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
+
+html_favicon = "assets/img/favicon.ico"
+
+# display the Sphinx attribution in the footer
+html_show_sphinx = False
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
-htmlhelp_basename = 'fontToolsDoc'
+htmlhelp_basename = "fontToolsDoc"
# -- Options for LaTeX output ---------------------------------------------
@@ -110,15 +119,12 @@ latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
-
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
-
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
-
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
@@ -128,8 +134,13 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
- (master_doc, 'fontTools.tex', u'fontTools Documentation',
- u'Just van Rossum, Behdad Esfahbod et al.', 'manual'),
+ (
+ master_doc,
+ "fontTools.tex",
+ u"fontTools Documentation",
+ u"Just van Rossum, Behdad Esfahbod et al.",
+ "manual",
+ )
]
@@ -137,10 +148,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
-man_pages = [
- (master_doc, 'fonttools', u'fontTools Documentation',
- [author], 1)
-]
+man_pages = [(master_doc, "fonttools", u"fontTools Documentation", [author], 1)]
# -- Options for Texinfo output -------------------------------------------
@@ -149,8 +157,13 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- (master_doc, 'fontTools', u'fontTools Documentation',
- author, 'fontTools', 'A library for manipulating fonts, written in Python.',
- 'Typography'),
+ (
+ master_doc,
+ "fontTools",
+ u"fontTools Documentation",
+ author,
+ "fontTools",
+ "A library for manipulating fonts, written in Python.",
+ "Typography",
+ )
]
-
diff --git a/Doc/source/cu2qu/index.rst b/Doc/source/cu2qu/index.rst
new file mode 100644
index 00000000..41730e54
--- /dev/null
+++ b/Doc/source/cu2qu/index.rst
@@ -0,0 +1,38 @@
+##########################################
+cu2qu: Cubic to quadratic curve conversion
+##########################################
+
+Routines for converting cubic curves to quadratic splines, suitable for use
+in OpenType to TrueType outline conversion.
+
+Conversion is carried out to a degree of tolerance provided by the user. While
+it is relatively easy to find the best *single* quadratic curve to represent a
+given cubic (see for example `this method from CAGD <https://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/>`_),
+the best-fit method may not be sufficiently accurate for type design.
+
+Instead, this method chops the cubic curve into multiple segments before
+converting each cubic segment to a quadratic, in order to ensure that the
+resulting spline fits within the given tolerance.
+
+The basic curve conversion routines are implemented in the
+:mod:`fontTools.cu2qu.cu2qu` module; the :mod:`fontTools.cu2qu.ufo` module
+applies these routines to all of the curves in a UFO file or files; while the
+:mod:`fontTools.cu2qu.cli` module implements the ``fonttools cu2qu`` command
+for converting a UFO format font with cubic curves into one with quadratic
+curves.
+
+fontTools.cu2qu.cu2qu
+---------------------
+
+.. automodule:: fontTools.cu2qu.cu2qu
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+fontTools.cu2qu.ufo
+-------------------
+
+.. automodule:: fontTools.cu2qu.ufo
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/designspaceLib/index.rst b/Doc/source/designspaceLib/index.rst
index 2fb4e528..2c33df7b 100644
--- a/Doc/source/designspaceLib/index.rst
+++ b/Doc/source/designspaceLib/index.rst
@@ -13,5 +13,6 @@ to have a reader and writer that are independent of a specific system.
scripting
.. automodule:: fontTools.designspaceLib
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/designspaceLib/readme.rst b/Doc/source/designspaceLib/readme.rst
index 2594084a..c5757a6e 100644
--- a/Doc/source/designspaceLib/readme.rst
+++ b/Doc/source/designspaceLib/readme.rst
@@ -2,23 +2,22 @@
DesignSpaceDocument Specification
#################################
-An object to read, write and edit interpolation systems for typefaces.
+An object to read, write and edit interpolation systems for typefaces. Define sources, axes, rules and instances.
-- the format was originally written for MutatorMath.
-- the format is now also used in fontTools.varlib.
-- Define sources, axes and instances.
-- Not all values might be required by all applications.
+- `The Python API of the objects <#python-api>`_
+- `The document XML structure <#document-xml-structure>`_
-A couple of differences between things that use designspaces:
-- Varlib does not support anisotropic interpolations.
-- MutatorMath and Superpolator will extrapolate over the boundaries of
- the axes. Varlib can not (at the moment).
-- Varlib requires much less data to define an instance than
- MutatorMath.
-- The goals of Varlib and MutatorMath are different, so not all
- attributes are always needed.
-- Need to expand the description of FDK use of designspace files.
+**********
+Python API
+**********
+
+
+
+.. _designspacedocument-object:
+
+DesignSpaceDocument object
+==========================
The DesignSpaceDocument object can read and write ``.designspace`` data.
It imports the axes, sources and instances to very basic **descriptor**
@@ -28,87 +27,75 @@ adding them to the document. This makes it easy to integrate this object
in different contexts.
The **DesignSpaceDocument** object can be subclassed to work with
-different objects, as long as they have the same attributes.
+different objects, as long as they have the same attributes. Reader and
+Writer objects can be subclassed as well.
+
+**Note:** Python attribute names are usually camelCased, the
+corresponding `XML <#document-xml-structure>`_ attributes are usually
+all lowercase.
+
+.. example-1:
.. code:: python
- from designSpaceDocument import DesignSpaceDocument
+ from fontTools.designspaceLib import DesignSpaceDocument
doc = DesignSpaceDocument()
doc.read("some/path/to/my.designspace")
doc.axes
doc.sources
doc.instances
-**********
-Validation
-**********
+Attributes
+----------
-Some validation is done when reading.
-
-Axes
-====
-
-- If the ``axes`` element is available in the document then all
- locations will check their dimensions against the defined axes. If a
- location uses an axis that is not defined it will be ignored.
-- If there are no ``axes`` in the document, locations will accept all
- axis names, so that we can..
-- Use ``doc.checkAxes()`` to reconstruct axes definitions based on the
- ``source.location`` values. If you save the document the axes will be
- there.
-
-Default font
-============
-
-- The source with the ``copyInfo`` flag indicates this is the default
- font.
-- In mutatorMath the default font is selected automatically. A warning
- is printed if the mutatorMath default selection differs from the one
- set by ``copyInfo``. But the ``copyInfo`` source will be used.
-- If no source has a ``copyInfo`` flag, mutatorMath will be used to
- select one. This source gets its ``copyInfo`` flag set. If you save
- the document this flag will be set.
-- Use ``doc.checkDefault()`` to set the default font.
-
-************
-Localisation
-************
-
-Some of the descriptors support localised names. The names are stored in
-dictionaries using the language code as key. That means that there are
-now two places to store names: the old attribute and the new localised
-dictionary, ``obj.stylename`` and ``obj.localisedStyleName['en']``.
-
-*****
-Rules
-*****
+- ``axes``: list of axisDescriptors
+- ``sources``: list of sourceDescriptors
+- ``instances``: list of instanceDescriptors
+- ``rules``: list if ruleDescriptors
+- ``readerClass``: class of the reader object
+- ``writerClass``: class of the writer object
+- ``lib``: dict for user defined, custom data that needs to be stored
+ in the designspace. Use reverse-DNS notation to identify your own data.
+ Respect the data stored by others.
+- ``rulesProcessingLast``: This flag indicates whether the substitution rules should be applied before or after other glyph substitution features. False: before, True: after.
+
+Methods
+-------
+
+- ``read(path)``: read a designspace file from ``path``
+- ``write(path)``: write this designspace to ``path``
+- ``addSource(aSourceDescriptor)``: add this sourceDescriptor to
+ ``doc.sources``.
+- ``addInstance(anInstanceDescriptor)``: add this instanceDescriptor
+ to ``doc.instances``.
+- ``addAxis(anAxisDescriptor)``: add this instanceDescriptor to ``doc.axes``.
+- ``newDefaultLocation()``: returns a dict with the default location
+ in designspace coordinates.
+- ``updateFilenameFromPath(masters=True, instances=True, force=False)``:
+ set a descriptor filename attr from the path and this document.
+- ``newAxisDescriptor()``: return a new axisDescriptor object.
+- ``newSourceDescriptor()``: return a new sourceDescriptor object.
+- ``newInstanceDescriptor()``: return a new instanceDescriptor object.
+- ``getAxisOrder()``: return a list of axisnames
+- ``findDefault()``: return the sourceDescriptor that is on the default
+ location. Returns None if there isn't one.
+- ``normalizeLocation(aLocation)``: return a dict with normalized axis values.
+- ``normalize()``: normalize the geometry of this designspace: scale all the
+ locations of all masters and instances to the ``-1 - 0 - 1`` value.
+- ``loadSourceFonts()``: Ensure SourceDescriptor.font attributes are loaded,
+ and return list of fonts.
+- ``tostring(encoding=None)``: Returns the designspace as a string. Default
+ encoding `utf-8`.
+
+Class Methods
+-------------
+- ``fromfile(path)``
+- ``fromstring(string)``
-Rules describe designspace areas in which one glyph should be replaced by another.
-A rule has a name and a number of conditionsets. The rule also contains a list of
-glyphname pairs: the glyphs that need to be substituted. For a rule to be triggered
-**only one** of the conditionsets needs to be true, ``OR``. Within a conditionset
-**all** conditions need to be true, ``AND``.
-The ``sub`` element contains a pair of glyphnames. The ``name`` attribute is the glyph that should be visible when the rule evaluates to **False**. The ``with`` attribute is the glyph that should be visible when the rule evaluates to **True**.
-UFO instances
-=============
-- When making instances as UFOs however, we need to swap the glyphs so
- that the original shape is still available. For instance, if a rule
- swaps ``a`` for ``a.alt``, but a glyph that references ``a`` in a
- component would then show the new ``a.alt``.
-- But that can lead to unexpected results. So, if there are no rules
- for ``adieresis`` (assuming it references ``a``) then that glyph
- **should not change appearance**. That means that when the rule swaps
- ``a`` and ``a.alt`` it also swaps all components that reference these
- glyphs so they keep their appearance.
-- The swap function also needs to take care of swapping the names in
- kerning data.
-**********
-Python API
-**********
SourceDescriptor object
=======================
@@ -138,8 +125,7 @@ Attributes
- ``copyLib``: bool. Indicates if the contents of the font.lib need to
be copied to the instances. MutatorMath.
- ``copyInfo`` bool. Indicates if the non-interpolating font.info needs
- to be copied to the instances. Also indicates this source is expected
- to be the default font. MutatorMath + Varlib
+ to be copied to the instances. MutatorMath
- ``copyGroups`` bool. Indicates if the groups need to be copied to the
instances. MutatorMath.
- ``copyFeatures`` bool. Indicates if the feature text needs to be
@@ -182,6 +168,7 @@ InstanceDescriptor object
.. attributes-1:
+
Attributes
----------
@@ -284,10 +271,9 @@ AxisDescriptor object
- ``default``: number. The default value for this axis, i.e. when a new
location is created, this is the value this axis will get in user
space. MutatorMath + Varlib.
-- ``map``: list of input / output values that can describe a warp of user space
- to design space coordinates. If no map values are present, it is assumed user
- space is the same as design space, as in [(minimum, minimum), (maximum, maximum)].
- Varlib.
+- ``map``: list of input / output values that can describe a warp
+ of user space to design space coordinates. If no map values are present, it is assumed user space is the same as design space, as
+ in [(minimum, minimum), (maximum, maximum)]. Varlib.
.. code:: python
@@ -311,15 +297,27 @@ RuleDescriptor object
- Each condition is a dict with ``name``, ``minimum`` and ``maximum`` keys.
- ``subs``: list of substitutions
- Each substitution is stored as tuples of glyphnames, e.g. ("a", "a.alt").
+- Note: By default, rules are applied first, before other text shaping/OpenType layout, as they are part of the `Required Variation Alternates OpenType feature <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#-tag-rvrn>`_. See `5.0 rules element`_ § Attributes.
+
+Evaluating rules
+----------------
+
+- ``evaluateRule(rule, location)``: Return True if any of the rule's conditionsets
+ matches the given location.
+- ``evaluateConditions(conditions, location)``: Return True if all the conditions
+ matches the given location.
+- ``processRules(rules, location, glyphNames)``: Apply all the rules to the list
+ of glyphNames. Return a new list of glyphNames with substitutions applied.
.. code:: python
r1 = RuleDescriptor()
r1.name = "unique.rule.name"
- r1.conditionsSets.append([dict(name="weight", minimum=-10, maximum=10), dict(...)])
- r1.conditionsSets.append([dict(...), dict(...)])
+ r1.conditionSets.append([dict(name="weight", minimum=-10, maximum=10), dict(...)])
+ r1.conditionSets.append([dict(...), dict(...)])
r1.subs.append(("a", "a.alt"))
+
.. _subclassing-descriptors:
Subclassing descriptors
@@ -400,9 +398,9 @@ Attributes
location elements.
- ``tag``: required, string, 4 letters. Some axis tags are registered
in the OpenType Specification.
-- ``minimum``: required, number. The minimum value for this axis.
-- ``maximum``: required, number. The maximum value for this axis.
-- ``default``: required, number. The default value for this axis.
+- ``minimum``: required, number. The minimum value for this axis, in user space coordinates.
+- ``maximum``: required, number. The maximum value for this axis, in user space coordinates.
+- ``default``: required, number. The default value for this axis, in user space coordinates.
- ``hidden``: optional, 0 or 1. Records whether this axis needs to be
hidden in interfaces.
@@ -433,7 +431,7 @@ Value
- The natural language name of this axis.
-.. example-1:
+.. example-2:
Example
-------
@@ -448,12 +446,12 @@ Example
1.2 map element
===============
-- Defines a single node in a series of input value / output value
- pairs.
+- Defines a single node in a series of input value (user space coordinate)
+ to output value (designspace coordinate) pairs.
- Together these values transform the designspace.
- Child of ``axis`` element.
-.. example-2:
+.. example-3:
Example
-------
@@ -507,7 +505,7 @@ Attributes
- ``yvalue``: optional, number. Separate value for anisotropic
interpolations.
-.. example-3:
+.. example-4:
Example
-------
@@ -524,8 +522,9 @@ Example
3. source element
=================
-- Defines a single font that contributes to the designspace.
+- Defines a single font or layer that contributes to the designspace.
- Child element of ``sources``
+- Location in designspace coordinates.
.. attributes-5:
@@ -584,9 +583,7 @@ There are two meanings for the ``lib`` element:
- Child element of ``source``
- Defines if the instances can inherit the non-interpolating font info
from this source.
-- MutatorMath + Varlib
-- NOTE: **This presence of this element indicates this source is to be
- the default font.**
+- MutatorMath
.. 33-features-element:
@@ -638,7 +635,7 @@ Attributes
include the kerning of this source in the calculation.
- MutatorMath only
-.. example-4:
+.. example-5:
Example
-------
@@ -669,6 +666,7 @@ Example
- MutatorMath uses the ``glyphs`` element to describe how certain
glyphs need different masters, mainly to describe the effects of
conditional rules in Superpolator.
+- Location in designspace coordinates.
.. attributes-8:
@@ -774,7 +772,7 @@ with an ``xml:lang`` attribute:
- stylemapstylename
- stylemapfamilyname
-.. example-5:
+.. example-6:
Example
-------
@@ -798,7 +796,7 @@ Attributes
- ``source``: the identifier name of the source this master glyph needs
to be loaded from
-.. example-6:
+.. example-7:
Example
-------
@@ -846,6 +844,21 @@ Example
- Container for ``rule`` elements
- The rules are evaluated in this order.
+Rules describe designspace areas in which one glyph should be replaced by another.
+A rule has a name and a number of conditionsets. The rule also contains a list of
+glyphname pairs: the glyphs that need to be substituted. For a rule to be triggered
+**only one** of the conditionsets needs to be true, ``OR``. Within a conditionset
+**all** conditions need to be true, ``AND``.
+
+.. attributes-11:
+
+Attributes
+----------
+
+- ``processing``: flag, optional. Valid values are [``first``, ``last``]. This flag indicates whether the substitution rules should be applied before or after other glyph substitution features.
+- If no ``processing`` attribute is given, interpret as ``first``, and put the substitution rule in the `rvrn` feature.
+- If ``processing`` is ``last``, put it in `rclt`.
+
.. 51-rule-element:
5.1 rule element
@@ -853,8 +866,8 @@ Example
- Defines a named rule.
- Each ``rule`` element contains one or more ``conditionset`` elements.
-- Only one ``conditionset`` needs to be true to trigger the rule.
-- All conditions in a ``conditionset`` must be true to make the ``conditionset`` true.
+- **Only one** ``conditionset`` needs to be true to trigger the rule.
+- **All** conditions in a ``conditionset`` must be true to make the ``conditionset`` true.
- For backwards compatibility a ``rule`` can contain ``condition`` elements outside of a conditionset. These are then understood to be part of a single, implied, ``conditionset``. Note: these conditions should be written wrapped in a conditionset.
- A rule element needs to contain one or more ``sub`` elements in order to be compiled to a variable font.
- Rules without sub elements should be ignored when compiling a font.
@@ -881,9 +894,10 @@ Attributes
=======================
- Child element of ``conditionset``
-- Between the ``minimum`` and ``maximum`` this rule is ``True``.
-- If ``minimum`` is not available, assume it is ``axis.minimum``.
-- If ``maximum`` is not available, assume it is ``axis.maximum``.
+- Between the ``minimum`` and ``maximum`` this condition is ``True``.
+- ``minimum`` and ``maximum`` are in designspace coordinates.
+- If ``minimum`` is not available, assume it is ``axis.minimum``, mapped to designspace coordinates.
+- If ``maximum`` is not available, assume it is ``axis.maximum``, mapped to designspace coordinates.
- The condition must contain at least a minimum or maximum or both.
.. attributes-12:
@@ -893,8 +907,8 @@ Attributes
- ``name``: string, required. Must match one of the defined ``axis``
name attributes.
-- ``minimum``: number, required*. The low value.
-- ``maximum``: number, required*. The high value.
+- ``minimum``: number, required*. The low value, in designspace coordinates.
+- ``maximum``: number, required*. The high value, in designspace coordinates.
.. 513-sub-element:
@@ -903,6 +917,9 @@ Attributes
- Child element of ``rule``.
- Defines which glyph to replace when the rule evaluates to **True**.
+- The ``sub`` element contains a pair of glyphnames. The ``name`` attribute is the glyph that should be visible when the rule evaluates to **False**. The ``with`` attribute is the glyph that should be visible when the rule evaluates to **True**.
+
+Axis values in Conditions are in designspace coordinates.
.. attributes-13:
@@ -914,7 +931,7 @@ Attributes
- ``with``: string, required. The name of the glyph it is replaced
with.
-.. example-7:
+.. example-8:
Example
-------
@@ -924,7 +941,7 @@ contained in a conditionset.
.. code:: xml
- <rules>
+ <rules processing="last">
<rule name="named.rule.1">
<condition minimum="250" maximum="750" name="weight" />
<condition minimum="50" maximum="100" name="width" />
@@ -1059,9 +1076,75 @@ any of the UFOs. If the lib key is empty or not present in the Designspace, all
glyphs should be exported, regardless of what the same lib key in any of the
UFOs says.
-.. 8-this-document:
+.. 8-implementation-and-differences:
+
+
+8 Implementation and differences
+================================
+
+The designspace format has gone through considerable development.
+
+ - the format was originally written for MutatorMath.
+ - the format is now also used in fontTools.varlib.
+ - not all values are be required by all implementations.
-8 This document
+8.1 Varlib vs. MutatorMath
+--------------------------
+
+There are some differences between the way MutatorMath and fontTools.varlib handle designspaces.
+
+ - Varlib does not support anisotropic interpolations.
+ - MutatorMath will extrapolate over the boundaries of
+ the axes. Varlib can not (at the moment).
+ - Varlib requires much less data to define an instance than
+ MutatorMath.
+ - The goals of Varlib and MutatorMath are different, so not all
+ attributes are always needed.
+
+8.2 Older versions
+------------------
+
+- In some implementations that preceed Variable Fonts, the `copyInfo`
+ flag in a source indicated the source was to be treated as the default.
+ This is no longer compatible with the assumption that the default font
+ is located on the default value of each axis.
+- Older implementations did not require axis records to be present in
+ the designspace file. The axis extremes for instance were generated
+ from the locations used in the sources. This is no longer possible.
+
+8.3 Rules and generating static UFO instances
+---------------------------------------------
+
+When making instances as UFOs from a designspace with rules, it can
+be useful to evaluate the rules so that the characterset of the ufo
+reflects, as much as possible, the state of a variable font when seen
+at the same location. This can be done by some swapping and renaming of
+glyphs.
+
+While useful for proofing or development work, it should be noted that
+swapping and renaming leaves the UFOs with glyphnames that are no longer
+descriptive. For instance, after a swap `dollar.bar` could contain a shape
+without a bar. Also, when the swapped glyphs are part of other GSUB variations
+it can become complex very quickly. So proceed with caution.
+
+ - Assuming `rulesProcessingLast = True`:
+ - We need to swap the glyphs so that the original shape is still available.
+ For instance, if a rule swaps ``a`` for ``a.alt``, a glyph
+ that references ``a`` in a component would then show the new ``a.alt``.
+ - But that can lead to unexpected results, the two glyphs may have different
+ widths or height. So, glyphs that are not specifically referenced in a rule
+ **should not change appearance**. That means that the implementation that swaps
+ ``a`` and ``a.alt`` also swap all components that reference these
+ glyphs in order to preserve their appearance.
+ - The swap function also needs to take care of swapping the names in
+ kerning data and any GPOS code.
+
+
+.. 9-this-document
+
+9 This document
===============
-- The package is rather new and changes are to be expected.
+- Changes are to be expected.
+
+
diff --git a/Doc/source/developer.rst b/Doc/source/developer.rst
new file mode 100644
index 00000000..3e259f0e
--- /dev/null
+++ b/Doc/source/developer.rst
@@ -0,0 +1,115 @@
+.. _developerinfo:
+.. image:: ../../Icons/FontToolsIconGreenCircle.png
+ :width: 200px
+ :height: 200px
+ :alt: Font Tools
+ :align: center
+
+
+fontTools Developer Information
+===============================
+
+If you would like to contribute to the development of fontTools, you can clone the repository from GitHub, install the package in 'editable' mode and modify the source code in place. We recommend creating a virtual environment, using the Python 3 `venv <https://docs.python.org/3/library/venv.html>`_ module::
+
+ # download the source code to 'fonttools' folder
+ git clone https://github.com/fonttools/fonttools.git
+ cd fonttools
+
+ # create new virtual environment called e.g. 'fonttools-venv', or anything you like
+ python -m venv fonttools-venv
+
+ # source the `activate` shell script to enter the environment (Un*x)
+ . fonttools-venv/bin/activate
+
+ # to activate the virtual environment in Windows `cmd.exe`, do
+ fonttools-venv\Scripts\activate.bat
+
+ # install in 'editable' mode
+ pip install -e .
+
+
+.. note::
+
+ To exit a Python virtual environment, enter the command ``deactivate``.
+
+Testing
+-------
+
+To run the test suite, you need to install `pytest <http://docs.pytest.org/en/latest/>`__.
+When you run the ``pytest`` command, the tests will run against the
+installed fontTools package, or the first one found in the
+``PYTHONPATH``.
+
+You can also use `tox <https://tox.readthedocs.io/en/latest/>`__ to
+automatically run tests on different Python versions in isolated virtual
+environments::
+
+ pip install tox
+ tox
+
+
+.. note::
+
+ When you run ``tox`` without arguments, the tests are executed for all the environments listed in the ``tox.ini`` ``envlist``. The Python versions that are not available on your system ``PATH`` will be skipped.
+
+You can specify a particular testing environment list via the ``-e`` option, or the ``TOXENV`` environment variable::
+
+ tox -e py36
+ TOXENV="py36-cov,htmlcov" tox
+
+
+Development Community
+---------------------
+
+fontTools development is ongoing in an active community of developers that includes professional developers employed at major software corporations and type foundries as well as hobbyists.
+
+Feature requests and bug reports are always welcome at https://github.com/fonttools/fonttools/issues/
+
+The best place for end-user and developer discussion about the fontTools project is the `fontTools gitter channel <https://gitter.im/fonttools-dev/Lobby>`_. There is also a development https://groups.google.com/d/forum/fonttools-dev mailing list for continuous integration notifications.
+
+
+History
+-------
+
+The fontTools project was started by Just van Rossum in 1999, and was
+maintained as an open source project at
+http://sourceforge.net/projects/fonttools/. In 2008, Paul Wise (pabs3)
+began helping Just with stability maintenance. In 2013 Behdad Esfahbod
+began a friendly fork, thoroughly reviewing the codebase and making
+changes at https://github.com/behdad/fonttools to add new features and
+support for new font formats.
+
+
+Acknowledgments
+---------------
+
+In alphabetical order:
+
+Olivier Berten, Samyak Bhuta, Erik van Blokland, Petr van Blokland,
+Jelle Bosma, Sascha Brawer, Tom Byrer, Frédéric Coiffier, Vincent
+Connare, Dave Crossland, Simon Daniels, Peter Dekkers, Behdad Esfahbod,
+Behnam Esfahbod, Hannes Famira, Sam Fishman, Matt Fontaine, Yannis
+Haralambous, Greg Hitchcock, Jeremie Hornus, Khaled Hosny, John Hudson,
+Denis Moyogo Jacquerye, Jack Jansen, Tom Kacvinsky, Jens Kutilek,
+Antoine Leca, Werner Lemberg, Tal Leming, Peter Lofting, Cosimo Lupo,
+Masaya Nakamura, Dave Opstad, Laurence Penney, Roozbeh Pournader, Garret
+Rieger, Read Roberts, Guido van Rossum, Just van Rossum, Andreas Seidel,
+Georg Seifert, Chris Simpkins, Miguel Sousa, Adam Twardoch, Adrien Tétar, Vitaly Volkov,
+Paul Wise.
+
+License
+-------
+
+`MIT license <https://github.com/fonttools/fonttools/blob/master/LICENSE>`_. See the full text of the license for details.
+
+.. |Travis Build Status| image:: https://travis-ci.org/fonttools/fonttools.svg
+ :target: https://travis-ci.org/fonttools/fonttools
+.. |Appveyor Build status| image:: https://ci.appveyor.com/api/projects/status/0f7fmee9as744sl7/branch/master?svg=true
+ :target: https://ci.appveyor.com/project/fonttools/fonttools/branch/master
+.. |Coverage Status| image:: https://codecov.io/gh/fonttools/fonttools/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/fonttools/fonttools
+.. |PyPI| image:: https://img.shields.io/pypi/v/fonttools.svg
+ :target: https://pypi.org/project/FontTools
+.. |Gitter Chat| image:: https://badges.gitter.im/fonttools-dev/Lobby.svg
+ :alt: Join the chat at https://gitter.im/fonttools-dev/Lobby
+ :target: https://gitter.im/fonttools-dev/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
diff --git a/Doc/source/encodings.rst b/Doc/source/encodings.rst
deleted file mode 100644
index 8bcd38a2..00000000
--- a/Doc/source/encodings.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-#########
-encodings
-#########
-
-.. automodule:: fontTools.encodings
- :members:
- :undoc-members:
-
-codecs
-------
-
-.. automodule:: fontTools.encodings.codecs
- :members:
- :undoc-members:
diff --git a/Doc/source/encodings/index.rst b/Doc/source/encodings/index.rst
new file mode 100644
index 00000000..32d13c70
--- /dev/null
+++ b/Doc/source/encodings/index.rst
@@ -0,0 +1,21 @@
+##################################################
+encodings: Support for OpenType-specific encodings
+##################################################
+
+fontTools includes support for some character encodings found in legacy Mac
+TrueType fonts. Many of these legacy encodings have found their way into the
+standard Python ``encodings`` library, but others still remain unimplemented.
+Importing ``fontTools.encodings.codecs`` will therefore add string ``encode``
+and ``decode`` support for the following encodings:
+
+* ``x_mac_japanese_ttx``
+* ``x_mac_trad_chinese_ttx``
+* ``x_mac_korean_ttx``
+* ``x_mac_simp_chinese_ttx``
+
+fontTools also includes a package (``fontTools.encodings.MacRoman``) which
+contains a mapping of glyph IDs to glyph names in the MacRoman character set::
+
+ >>> from fontTools.encodings.MacRoman import MacRoman
+ >>> MacRoman[26]
+ 'twosuperior'
diff --git a/Doc/source/feaLib.rst b/Doc/source/feaLib.rst
deleted file mode 100644
index ad69217c..00000000
--- a/Doc/source/feaLib.rst
+++ /dev/null
@@ -1,43 +0,0 @@
-######
-feaLib
-######
-
-.. automodule:: fontTools.feaLib
- :members:
- :undoc-members:
-
-ast
----
-
-.. automodule:: fontTools.feaLib.ast
- :members:
- :undoc-members:
-
-builder
--------
-
-.. automodule:: fontTools.feaLib.builder
- :members:
- :undoc-members:
-
-error
------
-
-.. automodule:: fontTools.feaLib.parser
- :members:
- :undoc-members:
-
-lexer
------
-
-.. automodule:: fontTools.feaLib.lexer
- :members:
- :undoc-members:
-
-parser
-------
-
-.. automodule:: fontTools.feaLib.parser
- :members:
- :undoc-members:
-
diff --git a/Doc/source/feaLib/index.rst b/Doc/source/feaLib/index.rst
new file mode 100644
index 00000000..61ac31f3
--- /dev/null
+++ b/Doc/source/feaLib/index.rst
@@ -0,0 +1,40 @@
+#########################################
+feaLib: Read/write OpenType feature files
+#########################################
+
+fontTools' ``feaLib`` allows for the creation and parsing of Adobe
+Font Development Kit for OpenType feature (``.fea``) files. The syntax
+of these files is described `here <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html>`_.
+
+The :class:`fontTools.feaLib.parser.Parser` class can be used to parse files
+into an abstract syntax tree, and from there the
+:class:`fontTools.feaLib.builder.Builder` class can add features to an existing
+font file. You can inspect the parsed syntax tree, walk the tree and do clever
+things with it, and also generate your own feature files programmatically, by
+using the classes in the :mod:`fontTools.feaLib.ast` module.
+
+Parsing
+-------
+
+.. autoclass:: fontTools.feaLib.parser.Parser
+ :members: parse
+ :member-order: bysource
+
+Building
+---------
+
+.. automodule:: fontTools.feaLib.builder
+ :members: addOpenTypeFeatures, addOpenTypeFeaturesFromString
+
+Generation/Interrogation
+------------------------
+
+.. _`glyph-containing object`:
+.. _`glyph-containing objects`:
+
+In the below, a **glyph-containing object** is an object of one of the following
+classes: :class:`GlyphName`, :class:`GlyphClass`, :class:`GlyphClassName`.
+
+.. automodule:: fontTools.feaLib.ast
+ :member-order: bysource
+ :members:
diff --git a/Doc/source/index.rst b/Doc/source/index.rst
index b3d44cf0..2162cc13 100644
--- a/Doc/source/index.rst
+++ b/Doc/source/index.rst
@@ -1,28 +1,152 @@
+.. image:: ../../Icons/FontToolsIconGreenCircle.png
+ :width: 200px
+ :height: 200px
+ :alt: Font Tools
+ :align: center
+
+
fontTools Docs
==============
+About
+-----
+
+fontTools is a family of libraries and utilities for manipulating fonts in Python.
+
+The project has an `MIT open-source license <https://github.com/fonttools/fonttools/blob/master/LICENSE>`_. Among other things this means you can use it free of charge.
+
+Installation
+------------
+
+.. note::
+
+ fontTools requires `Python <http://www.python.org/download/>`_ 3.6 or later.
+
+The package is listed in the Python Package Index (PyPI), so you can install it with `pip <https://pip.pypa.io/>`_::
+
+ pip install fonttools
+
+See the Optional Requirements section below for details about module-specific dependencies that must be installed in select cases.
+
+Utilities
+---------
+
+fontTools installs four command-line utilities:
+
+- ``pyftmerge``, a tool for merging fonts; see :py:mod:`fontTools.merge`
+- ``pyftsubset``, a tool for subsetting fonts; see :py:mod:`fontTools.subset`
+- ``ttx``, a tool for converting between OpenType binary fonts (OTF) and an XML representation (TTX); see :py:mod:`fontTools.ttx`
+- ``fonttools``, a "meta-tool" for accessing other components of the fontTools family.
+
+This last utility takes a subcommand, which could be one of:
+
+- ``cffLib.width``: Calculate optimum defaultWidthX/nominalWidthX values
+- ``cu2qu``: Convert a UFO font from cubic to quadratic curves
+- ``feaLib``: Add features from a feature file (.fea) into a OTF font
+- ``help``: Show this help
+- ``merge``: Merge multiple fonts into one
+- ``mtiLib``: Convert a FontDame OTL file to TTX XML
+- ``subset``: OpenType font subsetter and optimizer
+- ``ttLib.woff2``: Compress and decompress WOFF2 fonts
+- ``ttx``: Convert OpenType fonts to XML and back
+- ``varLib``: Build a variable font from a designspace file and masters
+- ``varLib.instancer``: Partially instantiate a variable font.
+- ``varLib.interpolatable``: Test for interpolatability issues between fonts
+- ``varLib.interpolate_layout``: Interpolate GDEF/GPOS/GSUB tables for a point on a designspace
+- ``varLib.models``: Normalize locations on a given designspace
+- ``varLib.mutator``: Instantiate a variation font
+- ``varLib.varStore``: Optimize a font's GDEF variation store
+
+Libraries
+---------
+
+The main library you will want to access when using fontTools for font
+engineering is likely to be :py:mod:`fontTools.ttLib`, which is the package
+for handling TrueType/OpenType fonts. However, there are many other
+libraries in the fontTools suite:
+
+- :py:mod:`fontTools.afmLib`: Module for reading and writing AFM files
+- :py:mod:`fontTools.agl`: Access to the Adobe Glyph List
+- :py:mod:`fontTools.cffLib`: Read/write tools for Adobe CFF fonts
+- :py:mod:`fontTools.colorLib`: Module for handling colors in CPAL/COLR fonts
+- :py:mod:`fontTools.cu2qu`: Module for cubic to quadratic conversion
+- :py:mod:`fontTools.designspaceLib`: Read and write designspace files
+- :py:mod:`fontTools.encodings`: Support for font-related character encodings
+- :py:mod:`fontTools.feaLib`: Read and read AFDKO feature files
+- :py:mod:`fontTools.fontBuilder`: Construct TTF/OTF fonts from scratch
+- :py:mod:`fontTools.merge`: Tools for merging font files
+- :py:mod:`fontTools.pens`: Various classes for manipulating glyph outlines
+- :py:mod:`fontTools.subset`: OpenType font subsetting and optimization
+- :py:mod:`fontTools.svgLib.path`: Library for drawing SVG paths onto glyphs
+- :py:mod:`fontTools.t1Lib`: Tools for PostScript Type 1 fonts (Python2 only)
+- :py:mod:`fontTools.ttx`: Module for converting between OTF and XML representation
+- :py:mod:`fontTools.ufoLib`: Module for reading and writing UFO files
+- :py:mod:`fontTools.unicodedata`: Convert between Unicode and OpenType script information
+- :py:mod:`fontTools.varLib`: Module for dealing with 'gvar'-style font variations
+- :py:mod:`fontTools.voltLib`: Module for dealing with Visual OpenType Layout Tool (VOLT) files
+
+A selection of sample Python programs using these libaries can be found in the `Snippets directory <https://github.com/fonttools/fonttools/blob/master/Snippets/>`_ of the fontTools repository.
+
+Optional Dependencies
+---------------------
+
+The fontTools package currently has no (required) external dependencies
+besides the modules included in the Python Standard Library.
+However, a few extra dependencies are required to unlock optional features
+in some of the library modules. See the :doc:`optional requirements <./optional>`
+page for more information.
+
+Developer information
+---------------------
+
+Information for developers can be found :doc:`here <./developer>`.
+
+License
+-------
+
+`MIT license <https://github.com/fonttools/fonttools/blob/master/LICENSE>`_. See the full text of the license for details.
+
+
+Table of Contents
+-----------------
+
.. toctree::
- :maxdepth: 1
+ :maxdepth: 2
+ :caption: Library
afmLib
agl
- cffLib
+ cffLib/index
+ colorLib/index
+ cu2qu/index
designspaceLib/index
- encodings
- feaLib
+ encodings/index
+ feaLib/index
merge
misc/index
+ mtiLib
+ otlLib/index
pens/index
- subset
+ subset/index
+ svgLib/index
t1Lib
ttLib/index
ttx
+ ufoLib/index
+ unicode
+ unicodedata/index
varLib/index
voltLib
-Indices and tables
-==================
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+.. |Travis Build Status| image:: https://travis-ci.org/fonttools/fonttools.svg
+ :target: https://travis-ci.org/fonttools/fonttools
+.. |Appveyor Build status| image:: https://ci.appveyor.com/api/projects/status/0f7fmee9as744sl7/branch/master?svg=true
+ :target: https://ci.appveyor.com/project/fonttools/fonttools/branch/master
+.. |Coverage Status| image:: https://codecov.io/gh/fonttools/fonttools/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/fonttools/fonttools
+.. |PyPI| image:: https://img.shields.io/pypi/v/fonttools.svg
+ :target: https://pypi.org/project/FontTools
+.. |Gitter Chat| image:: https://badges.gitter.im/fonttools-dev/Lobby.svg
+ :alt: Join the chat at https://gitter.im/fonttools-dev/Lobby
+ :target: https://gitter.im/fonttools-dev/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge \ No newline at end of file
diff --git a/Doc/source/merge.rst b/Doc/source/merge.rst
index 74cf9ade..31146155 100644
--- a/Doc/source/merge.rst
+++ b/Doc/source/merge.rst
@@ -1,7 +1,10 @@
-#####
-merge
-#####
+####################################
+merge: Merge multiple fonts into one
+####################################
-.. automodule:: fontTools.merge
+``fontTools.merge`` provides both a library and a command line interface
+(``fonttools merge``) for merging multiple fonts together.
+
+.. autoclass:: fontTools.merge.Merger
+ :inherited-members:
:members:
- :undoc-members:
diff --git a/Doc/source/misc/arrayTools.rst b/Doc/source/misc/arrayTools.rst
index acb2a515..d996cc20 100644
--- a/Doc/source/misc/arrayTools.rst
+++ b/Doc/source/misc/arrayTools.rst
@@ -1,7 +1,9 @@
-##########
-arrayTools
-##########
+#############################################
+arrayTools: Various array and rectangle tools
+#############################################
.. automodule:: fontTools.misc.arrayTools
+ :member-order: bysource
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/bezierTools.rst b/Doc/source/misc/bezierTools.rst
index c5b4d2a7..10ddc3aa 100644
--- a/Doc/source/misc/bezierTools.rst
+++ b/Doc/source/misc/bezierTools.rst
@@ -1,7 +1,8 @@
-###########
-bezierTools
-###########
+####################################################
+bezierTools: Routines for working with Bezier curves
+####################################################
.. automodule:: fontTools.misc.bezierTools
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/classifyTools.rst b/Doc/source/misc/classifyTools.rst
index b02b3509..38c35d4c 100644
--- a/Doc/source/misc/classifyTools.rst
+++ b/Doc/source/misc/classifyTools.rst
@@ -3,5 +3,6 @@ classifyTools
#############
.. automodule:: fontTools.misc.classifyTools
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/cliTools.rst b/Doc/source/misc/cliTools.rst
new file mode 100644
index 00000000..36b2aeb9
--- /dev/null
+++ b/Doc/source/misc/cliTools.rst
@@ -0,0 +1,8 @@
+###################################################################
+cliTools: Utilities for command-line interfaces and console scripts
+###################################################################
+
+.. automodule:: fontTools.misc.cliTools
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/misc/eexec.rst b/Doc/source/misc/eexec.rst
index 8506f863..b229d585 100644
--- a/Doc/source/misc/eexec.rst
+++ b/Doc/source/misc/eexec.rst
@@ -1,7 +1,8 @@
-#####
-eexec
-#####
+###############################################################
+eexec: PostScript charstring encryption and decryption routines
+###############################################################
.. automodule:: fontTools.misc.eexec
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/encodingTools.rst b/Doc/source/misc/encodingTools.rst
index ff29f668..4e4b7197 100644
--- a/Doc/source/misc/encodingTools.rst
+++ b/Doc/source/misc/encodingTools.rst
@@ -3,5 +3,6 @@ encodingTools
#############
.. automodule:: fontTools.misc.encodingTools
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/etree.rst b/Doc/source/misc/etree.rst
new file mode 100644
index 00000000..4679a1dd
--- /dev/null
+++ b/Doc/source/misc/etree.rst
@@ -0,0 +1,8 @@
+#####
+etree
+#####
+
+.. automodule:: fontTools.misc.etree
+ :inherited-members:
+ :members:
+ :undoc-members: \ No newline at end of file
diff --git a/Doc/source/misc/filenames.rst b/Doc/source/misc/filenames.rst
new file mode 100644
index 00000000..2ebef353
--- /dev/null
+++ b/Doc/source/misc/filenames.rst
@@ -0,0 +1,7 @@
+##########################################################
+filenames: Implements UFO User Name to File Name Algorithm
+##########################################################
+
+.. automodule:: fontTools.misc.filenames
+ :members: userNameToFileName
+ :undoc-members:
diff --git a/Doc/source/misc/fixedTools.rst b/Doc/source/misc/fixedTools.rst
index 30a1f028..d3785f43 100644
--- a/Doc/source/misc/fixedTools.rst
+++ b/Doc/source/misc/fixedTools.rst
@@ -1,7 +1,8 @@
-##########
-fixedTools
-##########
+######################################################
+fixedTools: Tools for working with fixed-point numbers
+######################################################
.. automodule:: fontTools.misc.fixedTools
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/index.rst b/Doc/source/misc/index.rst
index 29a7245c..bd7db09e 100644
--- a/Doc/source/misc/index.rst
+++ b/Doc/source/misc/index.rst
@@ -1,6 +1,9 @@
-####
-misc
-####
+##########################################################
+misc: Miscellaneous libraries helpful for font engineering
+##########################################################
+
+This is a collection of packages, most of which are used as internal support
+utilities by fontTools, but some of which may be more generally useful.
.. toctree::
:maxdepth: 2
@@ -8,16 +11,26 @@ misc
arrayTools
bezierTools
classifyTools
+ cliTools
eexec
encodingTools
+ etree
+ filenames
fixedTools
+ intTools
loggingTools
- sstruct
+ macCreatorType
+ macRes
+ plistlib
psCharStrings
+ psLib
+ psOperators
+ py23
+ sstruct
+ symfont
testTools
textTools
timeTools
transform
xmlReader
xmlWriter
-
diff --git a/Doc/source/misc/intTools.rst b/Doc/source/misc/intTools.rst
new file mode 100644
index 00000000..24ea231f
--- /dev/null
+++ b/Doc/source/misc/intTools.rst
@@ -0,0 +1,6 @@
+###############################################
+intTools: Tools for working with integer values
+###############################################
+
+.. automodule:: fontTools.misc.intTools
+ :members:
diff --git a/Doc/source/misc/loggingTools.rst b/Doc/source/misc/loggingTools.rst
index fb8eab58..157e0209 100644
--- a/Doc/source/misc/loggingTools.rst
+++ b/Doc/source/misc/loggingTools.rst
@@ -1,6 +1,6 @@
-############
-loggingTools
-############
+###################################################################
+loggingTools: tools for interfacing with the Python logging package
+###################################################################
.. automodule:: fontTools.misc.loggingTools
:members:
diff --git a/Doc/source/misc/macCreatorType.rst b/Doc/source/misc/macCreatorType.rst
new file mode 100644
index 00000000..809098dc
--- /dev/null
+++ b/Doc/source/misc/macCreatorType.rst
@@ -0,0 +1,9 @@
+##############################################################
+macCreatorType: Functions for working with Mac file attributes
+##############################################################
+
+This module requires the `xattr <https://pypi.org/project/xattr/>`_ module
+to be installed in order to function correctly.
+
+.. automodule:: fontTools.misc.macCreatorType
+ :members:
diff --git a/Doc/source/misc/macRes.rst b/Doc/source/misc/macRes.rst
new file mode 100644
index 00000000..6fce8e2e
--- /dev/null
+++ b/Doc/source/misc/macRes.rst
@@ -0,0 +1,11 @@
+############################################
+macRes: Tools for reading Mac resource forks
+############################################
+
+Classic Mac OS files are made up of two parts - the "data fork" which contains the file contents proper, and the "resource fork" which contains a number of structured data items called "resources". Some fonts, such as Mac "font suitcases" and Type 1 LWFN fonts, still use the resource fork for this kind of structured data, and so to read them, fontTools needs to have access to resource forks.
+
+The Inside Macintosh volume `More Macintosh Toolbox <https://developer.apple.com/library/archive/documentation/mac/pdf/MoreMacintoshToolbox.pdf#page=34>`_ explains the structure of resource and data forks.
+
+.. automodule:: fontTools.misc.macRes
+ :members: ResourceReader, Resource
+ :member-order: bysource \ No newline at end of file
diff --git a/Doc/source/misc/plistlib.rst b/Doc/source/misc/plistlib.rst
new file mode 100644
index 00000000..68570967
--- /dev/null
+++ b/Doc/source/misc/plistlib.rst
@@ -0,0 +1,9 @@
+#########################################
+plistlib: Tools for handling .plist files
+#########################################
+
+.. automodule:: fontTools.misc.plistlib
+ :members: totree, fromtree, load, loads, dump, dumps
+
+.. autoclass:: fontTools.misc.plistlib.Data
+ :members:
diff --git a/Doc/source/misc/psCharStrings.rst b/Doc/source/misc/psCharStrings.rst
index 3dc0dce3..58497f65 100644
--- a/Doc/source/misc/psCharStrings.rst
+++ b/Doc/source/misc/psCharStrings.rst
@@ -3,5 +3,6 @@ psCharStrings
#############
.. automodule:: fontTools.misc.psCharStrings
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/psLib.rst b/Doc/source/misc/psLib.rst
new file mode 100644
index 00000000..f3afa8bf
--- /dev/null
+++ b/Doc/source/misc/psLib.rst
@@ -0,0 +1,8 @@
+#####
+psLib
+#####
+
+.. automodule:: fontTools.misc.psLib
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/misc/psOperators.rst b/Doc/source/misc/psOperators.rst
new file mode 100644
index 00000000..432274e6
--- /dev/null
+++ b/Doc/source/misc/psOperators.rst
@@ -0,0 +1,8 @@
+###########
+psOperators
+###########
+
+.. automodule:: fontTools.misc.psOperators
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/misc/py23.rst b/Doc/source/misc/py23.rst
new file mode 100644
index 00000000..49a76bf1
--- /dev/null
+++ b/Doc/source/misc/py23.rst
@@ -0,0 +1,8 @@
+####
+py23
+####
+
+.. automodule:: fontTools.misc.py23
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/misc/sstruct.rst b/Doc/source/misc/sstruct.rst
index 482aa23f..0544795f 100644
--- a/Doc/source/misc/sstruct.rst
+++ b/Doc/source/misc/sstruct.rst
@@ -3,5 +3,6 @@ sstruct
#######
.. automodule:: fontTools.misc.sstruct
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/symfont.rst b/Doc/source/misc/symfont.rst
new file mode 100644
index 00000000..c189f3d9
--- /dev/null
+++ b/Doc/source/misc/symfont.rst
@@ -0,0 +1,8 @@
+#######
+symfont
+#######
+
+.. automodule:: fontTools.misc.symfont
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/misc/testTools.rst b/Doc/source/misc/testTools.rst
index b3215ac4..00197656 100644
--- a/Doc/source/misc/testTools.rst
+++ b/Doc/source/misc/testTools.rst
@@ -3,5 +3,6 @@ testTools
#########
.. automodule:: fontTools.misc.testTools
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/textTools.rst b/Doc/source/misc/textTools.rst
index 754eb675..0044c082 100644
--- a/Doc/source/misc/textTools.rst
+++ b/Doc/source/misc/textTools.rst
@@ -3,5 +3,6 @@ textTools
#########
.. automodule:: fontTools.misc.textTools
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/timeTools.rst b/Doc/source/misc/timeTools.rst
index f8d15081..c0a71990 100644
--- a/Doc/source/misc/timeTools.rst
+++ b/Doc/source/misc/timeTools.rst
@@ -3,5 +3,6 @@ timeTools
#########
.. automodule:: fontTools.misc.timeTools
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/transform.rst b/Doc/source/misc/transform.rst
index 9517fe02..44a3dbdf 100644
--- a/Doc/source/misc/transform.rst
+++ b/Doc/source/misc/transform.rst
@@ -3,5 +3,6 @@ transform
#########
.. automodule:: fontTools.misc.transform
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/xmlReader.rst b/Doc/source/misc/xmlReader.rst
index 6e093545..d0b80f5b 100644
--- a/Doc/source/misc/xmlReader.rst
+++ b/Doc/source/misc/xmlReader.rst
@@ -3,5 +3,6 @@ xmlReader
#########
.. automodule:: fontTools.misc.xmlReader
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/misc/xmlWriter.rst b/Doc/source/misc/xmlWriter.rst
index f488183d..5f7aaef5 100644
--- a/Doc/source/misc/xmlWriter.rst
+++ b/Doc/source/misc/xmlWriter.rst
@@ -3,5 +3,6 @@ xmlWriter
#########
.. automodule:: fontTools.misc.xmlWriter
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/mtiLib.rst b/Doc/source/mtiLib.rst
new file mode 100644
index 00000000..1bf74e18
--- /dev/null
+++ b/Doc/source/mtiLib.rst
@@ -0,0 +1,14 @@
+###########################################
+mtiLib: Read Monotype FontDame source files
+###########################################
+
+FontTools provides support for reading the OpenType layout tables produced by
+Monotype's FontDame and Font Chef font editors. These tables are written in a
+simple textual format. The ``mtiLib`` library parses these text files and creates
+table objects representing their contents.
+
+Additionally, ``fonttools mtiLib`` will convert a text file to TTX XML.
+
+
+.. automodule:: fontTools.mtiLib
+ :members: build, main
diff --git a/Doc/source/optional.rst b/Doc/source/optional.rst
new file mode 100644
index 00000000..09376a26
--- /dev/null
+++ b/Doc/source/optional.rst
@@ -0,0 +1,140 @@
+Optional Dependencies
+=====================
+
+The fonttools PyPI distribution also supports so-called "extras", i.e. a
+set of keywords that describe a group of additional dependencies, which can be
+used when installing via pip, or when specifying a requirement.
+For example:
+
+.. code:: sh
+
+ pip install fonttools[ufo,lxml,woff,unicode]
+
+This command will install fonttools, as well as the optional dependencies that
+are required to unlock the extra features named "ufo", etc.
+
+.. note::
+
+ Optional dependencies are detailed by module in the list below with the ``Extra`` setting that automates ``pip`` dependency installation when this is supported.
+
+
+
+:py:mod:`fontTools.misc.etree`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The module exports a ElementTree-like API for reading/writing XML files, and allows to use as the backend either the built-in ``xml.etree`` module or `lxml <https://lxml.de>`__. The latter is preferred whenever present, as it is generally faster and more secure.
+
+*Extra:* ``lxml``
+
+
+:py:mod:`fontTools.ufoLib`
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Package for reading and writing UFO source files; it requires:
+
+* `fs <https://pypi.org/pypi/fs>`__: (aka ``pyfilesystem2``) filesystem abstraction layer.
+
+* `enum34 <https://pypi.org/pypi/enum34>`__: backport for the built-in ``enum`` module (only required on Python < 3.4).
+
+*Extra:* ``ufo``
+
+
+:py:mod:`fontTools.ttLib.woff2`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Module to compress/decompress WOFF 2.0 web fonts; it requires:
+
+* `brotli <https://pypi.python.org/pypi/Brotli>`__: Python bindings of the Brotli compression library.
+
+*Extra:* ``woff``
+
+
+:py:mod:`fontTools.unicode`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To display the Unicode character names when dumping the ``cmap`` table
+with ``ttx`` we use the ``unicodedata`` module in the Standard Library.
+The version included in there varies between different Python versions.
+To use the latest available data, you can install:
+
+* `unicodedata2 <https://pypi.python.org/pypi/unicodedata2>`__: ``unicodedata`` backport for Python 2.7
+ and 3.x updated to the latest Unicode version 12.0. Note this is not necessary if you use Python 3.8
+ as the latter already comes with an up-to-date ``unicodedata``.
+
+*Extra:* ``unicode``
+
+
+:py:mod:`fontTools.varLib.interpolatable`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Module for finding wrong contour/component order between different masters.
+It requires one of the following packages in order to solve the so-called
+"minimum weight perfect matching problem in bipartite graphs", or
+the Assignment problem:
+
+* `scipy <https://pypi.python.org/pypi/scipy>`__: the Scientific Library for Python, which internally
+ uses `NumPy <https://pypi.python.org/pypi/numpy>`__ arrays and hence is very fast;
+* `munkres <https://pypi.python.org/pypi/munkres>`__: a pure-Python module that implements the Hungarian
+ or Kuhn-Munkres algorithm.
+
+*Extra:* ``interpolatable``
+
+
+:py:mod:`fontTools.varLib.plot`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Module for visualizing DesignSpaceDocument and resulting VariationModel.
+
+* `matplotlib <https://pypi.org/pypi/matplotlib>`__: 2D plotting library.
+
+*Extra:* ``plot``
+
+
+:py:mod:`fontTools.misc.symfont`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Advanced module for symbolic font statistics analysis; it requires:
+
+* `sympy <https://pypi.python.org/pypi/sympy>`__: the Python library for symbolic mathematics.
+
+*Extra:* ``symfont``
+
+
+:py:mod:`fontTools.t1Lib`
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To get the file creator and type of Macintosh PostScript Type 1 fonts
+on Python 3 you need to install the following module, as the old ``MacOS``
+module is no longer included in Mac Python:
+
+* `xattr <https://pypi.python.org/pypi/xattr>`__: Python wrapper for extended filesystem attributes
+ (macOS platform only).
+
+*Extra:* ``type1``
+
+
+:py:mod:`fontTools.pens.cocoaPen`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Pen for drawing glyphs with Cocoa ``NSBezierPath``, requires:
+
+* `PyObjC <https://pypi.python.org/pypi/pyobjc>`__: the bridge between Python and the Objective-C
+ runtime (macOS platform only).
+
+
+:py:mod:`fontTools.pens.qtPen`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Pen for drawing glyphs with Qt's ``QPainterPath``, requires:
+
+* `PyQt5 <https://pypi.python.org/pypi/PyQt5>`__: Python bindings for the Qt cross platform UI and
+ application toolkit.
+
+
+:py:mod:`fontTools.pens.reportLabPen`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Pen to drawing glyphs as PNG images, requires:
+
+* `reportlab <https://pypi.python.org/pypi/reportlab>`__: Python toolkit for generating PDFs and
+ graphics.
diff --git a/Doc/source/otlLib/index.rst b/Doc/source/otlLib/index.rst
new file mode 100644
index 00000000..1984914c
--- /dev/null
+++ b/Doc/source/otlLib/index.rst
@@ -0,0 +1,74 @@
+#################################################
+otlLib: Routines for working with OpenType Layout
+#################################################
+
+The ``fontTools.otlLib`` library provides routines to help you create the
+subtables and other data structures you need when you are editing a font's
+``GSUB`` and ``GPOS`` tables: substitution and positioning rules, anchors,
+lookups, coverage tables and so on.
+
+------------------------------------------
+High-level OpenType Layout Lookup Builders
+------------------------------------------
+
+.. automodule:: fontTools.otlLib.builder
+ :members: AlternateSubstBuilder, ChainContextPosBuilder, ChainContextSubstBuilder, LigatureSubstBuilder, MultipleSubstBuilder, CursivePosBuilder, MarkBasePosBuilder, MarkLigPosBuilder, MarkMarkPosBuilder, ReverseChainSingleSubstBuilder, SingleSubstBuilder, ClassPairPosSubtableBuilder, PairPosBuilder, SinglePosBuilder
+ :member-order: bysource
+
+--------------------------------------
+Common OpenType Layout Data Structures
+--------------------------------------
+
+.. automodule:: fontTools.otlLib.builder
+ :members: buildCoverage, buildLookup
+
+------------------------------------
+Low-level GSUB Table Lookup Builders
+------------------------------------
+
+These functions deal with the "simple" lookup types. See above for classes to
+help build more complex lookups (contextual and chaining lookups).
+
+.. automodule:: fontTools.otlLib.builder
+ :members: buildSingleSubstSubtable, buildMultipleSubstSubtable, buildAlternateSubstSubtable, buildLigatureSubstSubtable
+
+--------------------------
+GPOS Shared Table Builders
+--------------------------
+
+The functions help build the `GPOS shared tables <https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#shared-tables-value-record-anchor-table-and-mark-array-table>`_
+as defined in the OpenType spec: value records, anchors, mark arrays and
+mark record tables.
+
+.. automodule:: fontTools.otlLib.builder
+ :members: buildValue, buildAnchor, buildMarkArray, buildDevice, buildBaseArray, buildComponentRecord, buildMarkArray, buildValue
+ :member-order: bysource
+
+------------------------------------
+Low-level GPOS Table Lookup Builders
+------------------------------------
+
+These functions deal with the "simple" lookup types. See above for classes to
+help build more complex lookups (contextual and chaining lookups).
+
+.. automodule:: fontTools.otlLib.builder
+ :members: buildCursivePosSubtable, buildLigatureArray, buildMarkBasePos, buildMarkBasePosSubtable, buildMarkLigPos, buildMarkLigPosSubtable, buildPairPosClassesSubtable, buildPairPosGlyphs, buildPairPosGlyphsSubtable, buildSinglePos, buildSinglePosSubtable
+ :member-order: bysource
+
+----------------------------
+GDEF Table Subtable Builders
+----------------------------
+
+These functions build subtables for elements of the ``GDEF`` table.
+
+.. automodule:: fontTools.otlLib.builder
+ :members: buildAttachList, buildLigCaretList, buildMarkGlyphSetsDef
+ :member-order: bysource
+
+------------------
+STAT Table Builder
+------------------
+
+.. automodule:: fontTools.otlLib.builder
+ :members: buildStatTable
+ :member-order: bysource
diff --git a/Doc/source/pens/areaPen.rst b/Doc/source/pens/areaPen.rst
index 03a751be..f3f21bbb 100644
--- a/Doc/source/pens/areaPen.rst
+++ b/Doc/source/pens/areaPen.rst
@@ -3,5 +3,6 @@ areaPen
#######
.. automodule:: fontTools.pens.areaPen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/basePen.rst b/Doc/source/pens/basePen.rst
index f5965b16..87bf832b 100644
--- a/Doc/source/pens/basePen.rst
+++ b/Doc/source/pens/basePen.rst
@@ -3,5 +3,6 @@ basePen
#######
.. automodule:: fontTools.pens.basePen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/boundsPen.rst b/Doc/source/pens/boundsPen.rst
index 8de56201..a0d9ab41 100644
--- a/Doc/source/pens/boundsPen.rst
+++ b/Doc/source/pens/boundsPen.rst
@@ -3,5 +3,6 @@ boundsPen
#########
.. automodule:: fontTools.pens.boundsPen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/cocoaPen.rst b/Doc/source/pens/cocoaPen.rst
new file mode 100644
index 00000000..bbe8050e
--- /dev/null
+++ b/Doc/source/pens/cocoaPen.rst
@@ -0,0 +1,8 @@
+########
+cocoaPen
+########
+
+.. automodule:: fontTools.pens.cocoaPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/pens/cu2quPen.rst b/Doc/source/pens/cu2quPen.rst
new file mode 100644
index 00000000..4ae0249e
--- /dev/null
+++ b/Doc/source/pens/cu2quPen.rst
@@ -0,0 +1,8 @@
+########
+cu2quPen
+########
+
+.. automodule:: fontTools.pens.cu2quPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/pens/filterPen.rst b/Doc/source/pens/filterPen.rst
index 0b484a4c..c79b944c 100644
--- a/Doc/source/pens/filterPen.rst
+++ b/Doc/source/pens/filterPen.rst
@@ -3,5 +3,6 @@ filterPen
#########
.. automodule:: fontTools.pens.filterPen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/index.rst b/Doc/source/pens/index.rst
index 7e5a1926..91175cf7 100644
--- a/Doc/source/pens/index.rst
+++ b/Doc/source/pens/index.rst
@@ -5,14 +5,26 @@ pens
.. toctree::
:maxdepth: 1
+ areaPen
basePen
boundsPen
- pointInsidePen
+ cocoaPen
+ cu2quPen
filterPen
- transformPen
- t2CharStringPen
- statisticsPen
+ momentsPen
+ perimeterPen
+ pointInsidePen
+ pointPen
+ qtPen
recordingPen
+ reportLabPen
+ reverseContourPen
+ roundingPen
+ statisticsPen
+ svgPathPen
+ t2CharStringPen
teePen
- areaPen
- perimeterPen
+ transformPen
+ ttGlyphPen
+ wxPen
+
diff --git a/Doc/source/pens/momentsPen.rst b/Doc/source/pens/momentsPen.rst
new file mode 100644
index 00000000..4587f75b
--- /dev/null
+++ b/Doc/source/pens/momentsPen.rst
@@ -0,0 +1,8 @@
+##########
+momentsPen
+##########
+
+.. automodule:: fontTools.pens.momentsPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/pens/perimeterPen.rst b/Doc/source/pens/perimeterPen.rst
index 97feccaa..c625a3dc 100644
--- a/Doc/source/pens/perimeterPen.rst
+++ b/Doc/source/pens/perimeterPen.rst
@@ -3,5 +3,6 @@ perimeterPen
############
.. automodule:: fontTools.pens.perimeterPen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/pointInsidePen.rst b/Doc/source/pens/pointInsidePen.rst
index 9954e478..81a4b2e8 100644
--- a/Doc/source/pens/pointInsidePen.rst
+++ b/Doc/source/pens/pointInsidePen.rst
@@ -3,5 +3,6 @@ pointInsidePen
##############
.. automodule:: fontTools.pens.pointInsidePen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/pointPen.rst b/Doc/source/pens/pointPen.rst
new file mode 100644
index 00000000..09b98977
--- /dev/null
+++ b/Doc/source/pens/pointPen.rst
@@ -0,0 +1,8 @@
+########
+pointPen
+########
+
+.. automodule:: fontTools.pens.pointPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/pens/qtPen.rst b/Doc/source/pens/qtPen.rst
new file mode 100644
index 00000000..bfaa9b50
--- /dev/null
+++ b/Doc/source/pens/qtPen.rst
@@ -0,0 +1,8 @@
+#####
+qtPen
+#####
+
+.. automodule:: fontTools.pens.qtPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/pens/recordingPen.rst b/Doc/source/pens/recordingPen.rst
index 69e7ceb5..ee9178c5 100644
--- a/Doc/source/pens/recordingPen.rst
+++ b/Doc/source/pens/recordingPen.rst
@@ -3,5 +3,6 @@ recordingPen
############
.. automodule:: fontTools.pens.recordingPen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/reportLabPen.rst b/Doc/source/pens/reportLabPen.rst
new file mode 100644
index 00000000..7fe87847
--- /dev/null
+++ b/Doc/source/pens/reportLabPen.rst
@@ -0,0 +1,8 @@
+############
+reportLabPen
+############
+
+.. automodule:: fontTools.pens.reportLabPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/pens/reverseContourPen.rst b/Doc/source/pens/reverseContourPen.rst
new file mode 100644
index 00000000..8178e2ca
--- /dev/null
+++ b/Doc/source/pens/reverseContourPen.rst
@@ -0,0 +1,8 @@
+#################
+reverseContourPen
+#################
+
+.. automodule:: fontTools.pens.reverseContourPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/pens/roundingPen.rst b/Doc/source/pens/roundingPen.rst
new file mode 100644
index 00000000..7eb4214d
--- /dev/null
+++ b/Doc/source/pens/roundingPen.rst
@@ -0,0 +1,8 @@
+###########
+roundingPen
+###########
+
+.. automodule:: fontTools.pens.roundingPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/pens/statisticsPen.rst b/Doc/source/pens/statisticsPen.rst
index efa16089..e06e3220 100644
--- a/Doc/source/pens/statisticsPen.rst
+++ b/Doc/source/pens/statisticsPen.rst
@@ -3,5 +3,6 @@ statisticsPen
#############
.. automodule:: fontTools.pens.statisticsPen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/svgPathPen.rst b/Doc/source/pens/svgPathPen.rst
new file mode 100644
index 00000000..45bf1516
--- /dev/null
+++ b/Doc/source/pens/svgPathPen.rst
@@ -0,0 +1,8 @@
+##########
+svgPathPen
+##########
+
+.. automodule:: fontTools.pens.svgPathPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/pens/t2CharStringPen.rst b/Doc/source/pens/t2CharStringPen.rst
index d58c67aa..9d55391f 100644
--- a/Doc/source/pens/t2CharStringPen.rst
+++ b/Doc/source/pens/t2CharStringPen.rst
@@ -3,5 +3,6 @@ t2CharStringPen
###############
.. automodule:: fontTools.pens.t2CharStringPen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/teePen.rst b/Doc/source/pens/teePen.rst
index 7a0313c7..2a4558d5 100644
--- a/Doc/source/pens/teePen.rst
+++ b/Doc/source/pens/teePen.rst
@@ -3,5 +3,6 @@ teePen
######
.. automodule:: fontTools.pens.teePen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/transformPen.rst b/Doc/source/pens/transformPen.rst
index 5b414f84..3bb802a5 100644
--- a/Doc/source/pens/transformPen.rst
+++ b/Doc/source/pens/transformPen.rst
@@ -3,5 +3,6 @@ transformPen
############
.. automodule:: fontTools.pens.transformPen
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/pens/ttGlyphPen.rst b/Doc/source/pens/ttGlyphPen.rst
new file mode 100644
index 00000000..e1bf7010
--- /dev/null
+++ b/Doc/source/pens/ttGlyphPen.rst
@@ -0,0 +1,8 @@
+##########
+ttGlyphPen
+##########
+
+.. automodule:: fontTools.pens.ttGlyphPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/pens/wxPen.rst b/Doc/source/pens/wxPen.rst
new file mode 100644
index 00000000..37ce50ac
--- /dev/null
+++ b/Doc/source/pens/wxPen.rst
@@ -0,0 +1,8 @@
+#####
+wxPen
+#####
+
+.. automodule:: fontTools.pens.wxPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/subset/cff.rst b/Doc/source/subset/cff.rst
new file mode 100644
index 00000000..8c21c396
--- /dev/null
+++ b/Doc/source/subset/cff.rst
@@ -0,0 +1,8 @@
+###
+cff
+###
+
+.. automodule:: fontTools.subset.cff
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/subset.rst b/Doc/source/subset/index.rst
index a8fff95e..f32e0d42 100644
--- a/Doc/source/subset.rst
+++ b/Doc/source/subset/index.rst
@@ -2,6 +2,12 @@
subset
######
+.. toctree::
+ :maxdepth: 1
+
+ cff
+
.. automodule:: fontTools.subset
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/svgLib/index.rst b/Doc/source/svgLib/index.rst
new file mode 100644
index 00000000..f86ff0ad
--- /dev/null
+++ b/Doc/source/svgLib/index.rst
@@ -0,0 +1,8 @@
+######
+svgLib
+######
+
+.. toctree::
+ :maxdepth: 1
+
+ path/index
diff --git a/Doc/source/svgLib/path/index.rst b/Doc/source/svgLib/path/index.rst
new file mode 100644
index 00000000..0d7551b9
--- /dev/null
+++ b/Doc/source/svgLib/path/index.rst
@@ -0,0 +1,13 @@
+####
+path
+####
+
+.. toctree::
+ :maxdepth: 1
+
+ parser
+
+.. automodule:: fontTools.svgLib.path
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/svgLib/path/parser.rst b/Doc/source/svgLib/path/parser.rst
new file mode 100644
index 00000000..fc7e7ff1
--- /dev/null
+++ b/Doc/source/svgLib/path/parser.rst
@@ -0,0 +1,8 @@
+######
+parser
+######
+
+.. automodule:: fontTools.svgLib.path.parser
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/t1Lib.rst b/Doc/source/t1Lib.rst
index dcc04380..4436086f 100644
--- a/Doc/source/t1Lib.rst
+++ b/Doc/source/t1Lib.rst
@@ -3,5 +3,6 @@ t1Lib
#####
.. automodule:: fontTools.t1Lib
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/ttLib/index.rst b/Doc/source/ttLib/index.rst
index d2739348..4dfa2d66 100644
--- a/Doc/source/ttLib/index.rst
+++ b/Doc/source/ttLib/index.rst
@@ -7,9 +7,13 @@ ttLib
macUtils
sfnt
+ standardGlyphOrder
tables
+ ttCollection
+ ttFont
woff2
.. automodule:: fontTools.ttLib
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/ttLib/macUtils.rst b/Doc/source/ttLib/macUtils.rst
index cb014d76..356a911b 100644
--- a/Doc/source/ttLib/macUtils.rst
+++ b/Doc/source/ttLib/macUtils.rst
@@ -3,5 +3,6 @@ macUtils
########
.. automodule:: fontTools.ttLib.macUtils
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/ttLib/sfnt.rst b/Doc/source/ttLib/sfnt.rst
index 41e30322..29566ab7 100644
--- a/Doc/source/ttLib/sfnt.rst
+++ b/Doc/source/ttLib/sfnt.rst
@@ -3,5 +3,6 @@ sfnt
####
.. automodule:: fontTools.ttLib.sfnt
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/ttLib/standardGlyphOrder.rst b/Doc/source/ttLib/standardGlyphOrder.rst
new file mode 100644
index 00000000..ca2557bf
--- /dev/null
+++ b/Doc/source/ttLib/standardGlyphOrder.rst
@@ -0,0 +1,12 @@
+##################
+standardGlyphOrder
+##################
+
+.. automodule:: fontTools.ttLib.standardGlyphOrder
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+.. data:: fontTools.ttLib.standardGlyphOrder.standardGlyphOrder
+
+A Python list of "standard" glyphs required by post table formats 1.0 and 2.0.
diff --git a/Doc/source/ttLib/tables.rst b/Doc/source/ttLib/tables.rst
index 6ae705fb..45c76b8b 100644
--- a/Doc/source/ttLib/tables.rst
+++ b/Doc/source/ttLib/tables.rst
@@ -2,7 +2,114 @@
tables
######
+This folder is a subpackage of :py:mod:`fontTools.ttLib`. Each module here is a
+specialized TT/OT table converter: they can convert raw data
+to Python objects and vice versa. Usually you don't need to
+use the modules directly: they are imported and used
+automatically when needed by :py:mod:`fontTools.ttLib`.
+
+If you are writing you own table converter the following is
+important.
+
+The modules here have pretty strange names: this is due to the
+fact that we need to map TT table tags (which are case sensitive)
+to filenames (which on Mac and Win aren't case sensitive) as well
+as to Python identifiers. The latter means it can only contain
+[A-Za-z0-9_] and cannot start with a number.
+
+:py:mod:`fontTools.ttLib` provides functions to expand a tag into the format used here::
+
+ >>> from fontTools import ttLib
+ >>> ttLib.tagToIdentifier("FOO ")
+ 'F_O_O_'
+ >>> ttLib.tagToIdentifier("cvt ")
+ '_c_v_t'
+ >>> ttLib.tagToIdentifier("OS/2")
+ 'O_S_2f_2'
+ >>> ttLib.tagToIdentifier("glyf")
+ '_g_l_y_f'
+ >>>
+
+And vice versa::
+
+ >>> ttLib.identifierToTag("F_O_O_")
+ 'FOO '
+ >>> ttLib.identifierToTag("_c_v_t")
+ 'cvt '
+ >>> ttLib.identifierToTag("O_S_2f_2")
+ 'OS/2'
+ >>> ttLib.identifierToTag("_g_l_y_f")
+ 'glyf'
+ >>>
+
+Eg. the 'glyf' table converter lives in a Python file called::
+
+ _g_l_y_f.py
+
+The converter itself is a class, named "table_" + expandedtag. Eg::
+
+
+ class table__g_l_y_f:
+ etc.
+
+
+Note that if you _do_ need to use such modules or classes manually,
+there are two convenient API functions that let you find them by tag::
+
+ >>> ttLib.getTableModule('glyf')
+ <module 'ttLib.tables._g_l_y_f'>
+ >>> ttLib.getTableClass('glyf')
+ <class ttLib.tables._g_l_y_f.table__g_l_y_f at 645f400>
+ >>
+
+You must subclass from :py:mod:`fontTools.ttLib.tables.DefaultTable.DefaultTable`. It provides some default
+behavior, as well as a constructor method (__init__) that you don't need to
+override.
+
+Your converter should minimally provide two methods::
+
+
+ class table_F_O_O_(DefaultTable.DefaultTable): # converter for table 'FOO '
+
+ def decompile(self, data, ttFont):
+ # 'data' is the raw table data. Unpack it into a
+ # Python data structure.
+ # 'ttFont' is a ttLib.TTfile instance, enabling you to
+ # refer to other tables. Do ***not*** keep a reference to
+ # it: it will cause a circular reference (ttFont saves
+ # a reference to us), and that means we'll be leaking
+ # memory. If you need to use it in other methods, just
+ # pass it around as a method argument.
+
+ def compile(self, ttFont):
+ # Return the raw data, as converted from the Python
+ # data structure.
+ # Again, 'ttFont' is there so you can access other tables.
+ # Same warning applies.
+
+
+If you want to support TTX import/export as well, you need to provide two
+additional methods::
+
+
+ def toXML(self, writer, ttFont):
+ # XXX
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ # XXX
+
+
+
.. automodule:: fontTools.ttLib.tables
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+_a_n_k_r
+--------
+
+.. automodule:: fontTools.ttLib.tables._a_n_k_r
+ :inherited-members:
:members:
:undoc-members:
@@ -10,6 +117,23 @@ _a_v_a_r
--------
.. automodule:: fontTools.ttLib.tables._a_v_a_r
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+_b_s_l_n
+--------
+
+.. automodule:: fontTools.ttLib.tables._b_s_l_n
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+_c_i_d_g
+--------
+
+.. automodule:: fontTools.ttLib.tables._c_i_d_g
+ :inherited-members:
:members:
:undoc-members:
@@ -17,6 +141,7 @@ _c_m_a_p
--------
.. automodule:: fontTools.ttLib.tables._c_m_a_p
+ :inherited-members:
:members:
:undoc-members:
@@ -24,6 +149,7 @@ _c_v_a_r
--------
.. automodule:: fontTools.ttLib.tables._c_v_a_r
+ :inherited-members:
:members:
:undoc-members:
@@ -31,6 +157,7 @@ _c_v_t
------
.. automodule:: fontTools.ttLib.tables._c_v_t
+ :inherited-members:
:members:
:undoc-members:
@@ -38,6 +165,7 @@ _f_e_a_t
--------
.. automodule:: fontTools.ttLib.tables._f_e_a_t
+ :inherited-members:
:members:
:undoc-members:
@@ -45,6 +173,7 @@ _f_p_g_m
--------
.. automodule:: fontTools.ttLib.tables._f_p_g_m
+ :inherited-members:
:members:
:undoc-members:
@@ -52,6 +181,7 @@ _f_v_a_r
--------
.. automodule:: fontTools.ttLib.tables._f_v_a_r
+ :inherited-members:
:members:
:undoc-members:
@@ -59,6 +189,16 @@ _g_a_s_p
--------
.. automodule:: fontTools.ttLib.tables._g_a_s_p
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+
+_g_c_i_d
+--------
+
+.. automodule:: fontTools.ttLib.tables._g_c_i_d
+ :inherited-members:
:members:
:undoc-members:
@@ -66,6 +206,7 @@ _g_l_y_f
--------
.. automodule:: fontTools.ttLib.tables._g_l_y_f
+ :inherited-members:
:members:
:undoc-members:
@@ -73,6 +214,7 @@ _g_v_a_r
--------
.. automodule:: fontTools.ttLib.tables._g_v_a_r
+ :inherited-members:
:members:
:undoc-members:
@@ -80,6 +222,7 @@ _h_d_m_x
--------
.. automodule:: fontTools.ttLib.tables._h_d_m_x
+ :inherited-members:
:members:
:undoc-members:
@@ -87,6 +230,7 @@ _h_e_a_d
--------
.. automodule:: fontTools.ttLib.tables._h_e_a_d
+ :inherited-members:
:members:
:undoc-members:
@@ -94,6 +238,7 @@ _h_h_e_a
--------
.. automodule:: fontTools.ttLib.tables._h_h_e_a
+ :inherited-members:
:members:
:undoc-members:
@@ -101,6 +246,7 @@ _h_m_t_x
--------
.. automodule:: fontTools.ttLib.tables._h_m_t_x
+ :inherited-members:
:members:
:undoc-members:
@@ -108,6 +254,15 @@ _k_e_r_n
--------
.. automodule:: fontTools.ttLib.tables._k_e_r_n
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+_l_c_a_r
+--------
+
+.. automodule:: fontTools.ttLib.tables._l_c_a_r
+ :inherited-members:
:members:
:undoc-members:
@@ -115,6 +270,7 @@ _l_o_c_a
--------
.. automodule:: fontTools.ttLib.tables._l_o_c_a
+ :inherited-members:
:members:
:undoc-members:
@@ -122,6 +278,7 @@ _l_t_a_g
--------
.. automodule:: fontTools.ttLib.tables._l_t_a_g
+ :inherited-members:
:members:
:undoc-members:
@@ -129,6 +286,7 @@ _m_a_x_p
--------
.. automodule:: fontTools.ttLib.tables._m_a_x_p
+ :inherited-members:
:members:
:undoc-members:
@@ -136,6 +294,24 @@ _m_e_t_a
--------
.. automodule:: fontTools.ttLib.tables._m_e_t_a
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+_m_o_r_t
+--------
+
+.. automodule:: fontTools.ttLib.tables._m_o_r_t
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+
+_m_o_r_x
+--------
+
+.. automodule:: fontTools.ttLib.tables._m_o_r_x
+ :inherited-members:
:members:
:undoc-members:
@@ -143,6 +319,15 @@ _n_a_m_e
--------
.. automodule:: fontTools.ttLib.tables._n_a_m_e
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+_o_p_b_d
+--------
+
+.. automodule:: fontTools.ttLib.tables._o_p_b_d
+ :inherited-members:
:members:
:undoc-members:
@@ -150,6 +335,7 @@ _p_o_s_t
--------
.. automodule:: fontTools.ttLib.tables._p_o_s_t
+ :inherited-members:
:members:
:undoc-members:
@@ -157,6 +343,16 @@ _p_r_e_p
--------
.. automodule:: fontTools.ttLib.tables._p_r_e_p
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+
+_p_r_o_p
+--------
+
+.. automodule:: fontTools.ttLib.tables._p_r_o_p
+ :inherited-members:
:members:
:undoc-members:
@@ -164,6 +360,7 @@ _s_b_i_x
--------
.. automodule:: fontTools.ttLib.tables._s_b_i_x
+ :inherited-members:
:members:
:undoc-members:
@@ -171,6 +368,7 @@ _t_r_a_k
--------
.. automodule:: fontTools.ttLib.tables._t_r_a_k
+ :inherited-members:
:members:
:undoc-members:
@@ -178,6 +376,7 @@ _v_h_e_a
--------
.. automodule:: fontTools.ttLib.tables._v_h_e_a
+ :inherited-members:
:members:
:undoc-members:
@@ -185,6 +384,7 @@ _v_m_t_x
--------
.. automodule:: fontTools.ttLib.tables._v_m_t_x
+ :inherited-members:
:members:
:undoc-members:
@@ -192,6 +392,7 @@ asciiTable
----------
.. automodule:: fontTools.ttLib.tables.asciiTable
+ :inherited-members:
:members:
:undoc-members:
@@ -199,6 +400,7 @@ B_A_S_E_
--------
.. automodule:: fontTools.ttLib.tables.B_A_S_E_
+ :inherited-members:
:members:
:undoc-members:
@@ -206,6 +408,7 @@ BitmapGlyphMetrics
------------------
.. automodule:: fontTools.ttLib.tables.BitmapGlyphMetrics
+ :inherited-members:
:members:
:undoc-members:
@@ -213,6 +416,7 @@ C_B_D_T_
--------
.. automodule:: fontTools.ttLib.tables.C_B_D_T_
+ :inherited-members:
:members:
:undoc-members:
@@ -220,6 +424,7 @@ C_B_L_C_
--------
.. automodule:: fontTools.ttLib.tables.C_B_L_C_
+ :inherited-members:
:members:
:undoc-members:
@@ -227,6 +432,7 @@ C_F_F_
------
.. automodule:: fontTools.ttLib.tables.C_F_F_
+ :inherited-members:
:members:
:undoc-members:
@@ -234,6 +440,7 @@ C_F_F__2
--------
.. automodule:: fontTools.ttLib.tables.C_F_F__2
+ :inherited-members:
:members:
:undoc-members:
@@ -241,6 +448,7 @@ C_O_L_R_
--------
.. automodule:: fontTools.ttLib.tables.C_O_L_R_
+ :inherited-members:
:members:
:undoc-members:
@@ -248,6 +456,7 @@ C_P_A_L_
--------
.. automodule:: fontTools.ttLib.tables.C_P_A_L_
+ :inherited-members:
:members:
:undoc-members:
@@ -255,6 +464,7 @@ D_S_I_G_
--------
.. automodule:: fontTools.ttLib.tables.D_S_I_G_
+ :inherited-members:
:members:
:undoc-members:
@@ -262,6 +472,7 @@ DefaultTable
------------
.. automodule:: fontTools.ttLib.tables.DefaultTable
+ :inherited-members:
:members:
:undoc-members:
@@ -269,6 +480,7 @@ E_B_D_T_
--------
.. automodule:: fontTools.ttLib.tables.E_B_D_T_
+ :inherited-members:
:members:
:undoc-members:
@@ -276,13 +488,41 @@ E_B_L_C_
--------
.. automodule:: fontTools.ttLib.tables.E_B_L_C_
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+F__e_a_t
+--------
+
+.. automodule:: fontTools.ttLib.tables.F__e_a_t
+ :inherited-members:
:members:
:undoc-members:
+
F_F_T_M_
--------
.. automodule:: fontTools.ttLib.tables.F_F_T_M_
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+
+G__l_a_t
+--------
+
+.. automodule:: fontTools.ttLib.tables.G__l_a_t
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+G__l_o_c
+--------
+
+.. automodule:: fontTools.ttLib.tables.G__l_o_c
+ :inherited-members:
:members:
:undoc-members:
@@ -290,6 +530,7 @@ G_D_E_F_
--------
.. automodule:: fontTools.ttLib.tables.G_D_E_F_
+ :inherited-members:
:members:
:undoc-members:
@@ -297,6 +538,7 @@ G_M_A_P_
--------
.. automodule:: fontTools.ttLib.tables.G_M_A_P_
+ :inherited-members:
:members:
:undoc-members:
@@ -304,6 +546,7 @@ G_P_K_G_
--------
.. automodule:: fontTools.ttLib.tables.G_P_K_G_
+ :inherited-members:
:members:
:undoc-members:
@@ -311,6 +554,7 @@ G_P_O_S_
--------
.. automodule:: fontTools.ttLib.tables.G_P_O_S_
+ :inherited-members:
:members:
:undoc-members:
@@ -318,6 +562,15 @@ G_S_U_B_
--------
.. automodule:: fontTools.ttLib.tables.G_S_U_B_
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+grUtils
+-------
+
+.. automodule:: fontTools.ttLib.tables.grUtils
+ :inherited-members:
:members:
:undoc-members:
@@ -325,6 +578,7 @@ H_V_A_R_
--------
.. automodule:: fontTools.ttLib.tables.H_V_A_R_
+ :inherited-members:
:members:
:undoc-members:
@@ -332,6 +586,7 @@ J_S_T_F_
--------
.. automodule:: fontTools.ttLib.tables.J_S_T_F_
+ :inherited-members:
:members:
:undoc-members:
@@ -339,6 +594,7 @@ L_T_S_H_
--------
.. automodule:: fontTools.ttLib.tables.L_T_S_H_
+ :inherited-members:
:members:
:undoc-members:
@@ -346,6 +602,7 @@ M_A_T_H_
--------
.. automodule:: fontTools.ttLib.tables.M_A_T_H_
+ :inherited-members:
:members:
:undoc-members:
@@ -353,6 +610,7 @@ M_E_T_A_
--------
.. automodule:: fontTools.ttLib.tables.M_E_T_A_
+ :inherited-members:
:members:
:undoc-members:
@@ -360,6 +618,7 @@ M_V_A_R_
--------
.. automodule:: fontTools.ttLib.tables.M_V_A_R_
+ :inherited-members:
:members:
:undoc-members:
@@ -367,6 +626,7 @@ O_S_2f_2
--------
.. automodule:: fontTools.ttLib.tables.O_S_2f_2
+ :inherited-members:
:members:
:undoc-members:
@@ -374,6 +634,7 @@ otBase
------
.. automodule:: fontTools.ttLib.tables.otBase
+ :inherited-members:
:members:
:undoc-members:
@@ -381,6 +642,7 @@ otConverters
------------
.. automodule:: fontTools.ttLib.tables.otConverters
+ :inherited-members:
:members:
:undoc-members:
@@ -388,6 +650,7 @@ otData
------
.. automodule:: fontTools.ttLib.tables.otData
+ :inherited-members:
:members:
:undoc-members:
@@ -395,6 +658,23 @@ otTables
--------
.. automodule:: fontTools.ttLib.tables.otTables
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+S__i_l_f
+--------
+
+.. automodule:: fontTools.ttLib.tables.S__i_l_f
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+S__i_l_l
+--------
+
+.. automodule:: fontTools.ttLib.tables.S__i_l_l
+ :inherited-members:
:members:
:undoc-members:
@@ -402,6 +682,7 @@ S_I_N_G_
--------
.. automodule:: fontTools.ttLib.tables.S_I_N_G_
+ :inherited-members:
:members:
:undoc-members:
@@ -409,6 +690,7 @@ S_T_A_T_
--------
.. automodule:: fontTools.ttLib.tables.S_T_A_T_
+ :inherited-members:
:members:
:undoc-members:
@@ -416,6 +698,7 @@ S_V_G_
------
.. automodule:: fontTools.ttLib.tables.S_V_G_
+ :inherited-members:
:members:
:undoc-members:
@@ -423,6 +706,7 @@ sbixGlyph
---------
.. automodule:: fontTools.ttLib.tables.sbixGlyph
+ :inherited-members:
:members:
:undoc-members:
@@ -430,6 +714,7 @@ sbixStrike
----------
.. automodule:: fontTools.ttLib.tables.sbixStrike
+ :inherited-members:
:members:
:undoc-members:
@@ -437,6 +722,7 @@ T_S_I__0
--------
.. automodule:: fontTools.ttLib.tables.T_S_I__0
+ :inherited-members:
:members:
:undoc-members:
@@ -444,6 +730,7 @@ T_S_I__1
--------
.. automodule:: fontTools.ttLib.tables.T_S_I__1
+ :inherited-members:
:members:
:undoc-members:
@@ -451,6 +738,7 @@ T_S_I__2
--------
.. automodule:: fontTools.ttLib.tables.T_S_I__2
+ :inherited-members:
:members:
:undoc-members:
@@ -458,6 +746,7 @@ T_S_I__3
--------
.. automodule:: fontTools.ttLib.tables.T_S_I__3
+ :inherited-members:
:members:
:undoc-members:
@@ -465,6 +754,71 @@ T_S_I__5
--------
.. automodule:: fontTools.ttLib.tables.T_S_I__5
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+T_S_I_B_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_B_
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+T_S_I_C_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_C_
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+T_S_I_D_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_D_
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+T_S_I_J_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_J_
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+T_S_I_P_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_P_
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+T_S_I_S_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_S_
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+T_S_I_V_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_V_
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+T_T_F_A_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_T_F_A_
+ :inherited-members:
:members:
:undoc-members:
@@ -472,6 +826,7 @@ ttProgram
---------
.. automodule:: fontTools.ttLib.tables.ttProgram
+ :inherited-members:
:members:
:undoc-members:
@@ -479,6 +834,7 @@ TupleVariation
--------------
.. automodule:: fontTools.ttLib.tables.TupleVariation
+ :inherited-members:
:members:
:undoc-members:
@@ -486,6 +842,7 @@ V_D_M_X_
--------
.. automodule:: fontTools.ttLib.tables.V_D_M_X_
+ :inherited-members:
:members:
:undoc-members:
@@ -493,6 +850,7 @@ V_O_R_G_
--------
.. automodule:: fontTools.ttLib.tables.V_O_R_G_
+ :inherited-members:
:members:
:undoc-members:
@@ -500,6 +858,7 @@ V_V_A_R_
--------
.. automodule:: fontTools.ttLib.tables.V_V_A_R_
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/ttLib/ttCollection.rst b/Doc/source/ttLib/ttCollection.rst
new file mode 100644
index 00000000..0ca4ebd0
--- /dev/null
+++ b/Doc/source/ttLib/ttCollection.rst
@@ -0,0 +1,8 @@
+############
+ttCollection
+############
+
+.. automodule:: fontTools.ttLib.ttCollection
+ :inherited-members:
+ :members:
+ :undoc-members: \ No newline at end of file
diff --git a/Doc/source/ttLib/ttFont.rst b/Doc/source/ttLib/ttFont.rst
new file mode 100644
index 00000000..a571050c
--- /dev/null
+++ b/Doc/source/ttLib/ttFont.rst
@@ -0,0 +1,9 @@
+######
+ttFont
+######
+
+.. automodule:: fontTools.ttLib.ttFont
+ :inherited-members:
+ :members:
+ :undoc-members:
+ :private-members:
diff --git a/Doc/source/ttLib/woff2.rst b/Doc/source/ttLib/woff2.rst
index 0c464be6..327bb5b2 100644
--- a/Doc/source/ttLib/woff2.rst
+++ b/Doc/source/ttLib/woff2.rst
@@ -3,5 +3,6 @@ woff2
#####
.. automodule:: fontTools.ttLib.woff2
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/ttx.rst b/Doc/source/ttx.rst
index 1c909015..b6e43f5d 100644
--- a/Doc/source/ttx.rst
+++ b/Doc/source/ttx.rst
@@ -2,6 +2,61 @@
ttx
###
+
+TTX – From OpenType and TrueType to XML and Back
+------------------------------------------------
+
+Once installed you can use the ttx command to convert binary font files (.otf, .ttf, etc) to the TTX XML format, edit them, and convert them back to binary format. TTX files have a .ttx file extension::
+
+ ttx /path/to/font.otf
+ ttx /path/to/font.ttx
+
+The TTX application can be used in two ways, depending on what platform you run it on:
+
+* As a command line tool (Windows/DOS, Unix, macOS)
+* By dropping files onto the application (Windows, macOS)
+
+TTX detects what kind of files it is fed: it will output a ``.ttx`` file when it sees a ``.ttf`` or ``.otf``, and it will compile a ``.ttf`` or ``.otf`` when the input file is a ``.ttx`` file. By default, the output file is created in the same folder as the input file, and will have the same name as the input file but with a different extension. TTX will never overwrite existing files, but if necessary will append a unique number to the output filename (before the extension) such as ``Arial#1.ttf``.
+
+When using TTX from the command line there are a bunch of extra options. These are explained in the help text, as displayed when typing ``ttx -h`` at the command prompt. These additional options include:
+
+
+* specifying the folder where the output files are created
+* specifying which tables to dump or which tables to exclude
+* merging partial .ttx files with existing .ttf or .otf files
+* listing brief table info instead of dumping to .ttx
+* splitting tables to separate .ttx files
+* disabling TrueType instruction disassembly
+
+The TTX file format
+^^^^^^^^^^^^^^^^^^^
+
+.. begin table list
+
+The following tables are currently supported::
+
+ BASE, CBDT, CBLC, CFF, CFF2, COLR, CPAL, DSIG, Debg, EBDT, EBLC,
+ FFTM, Feat, GDEF, GMAP, GPKG, GPOS, GSUB, Glat, Gloc, HVAR, JSTF,
+ LTSH, MATH, META, MVAR, OS/2, SING, STAT, SVG, Silf, Sill, TSI0,
+ TSI1, TSI2, TSI3, TSI5, TSIB, TSIC, TSID, TSIJ, TSIP, TSIS, TSIV,
+ TTFA, VDMX, VORG, VVAR, ankr, avar, bsln, cidg, cmap, cvar, cvt,
+ feat, fpgm, fvar, gasp, gcid, glyf, gvar, hdmx, head, hhea, hmtx,
+ kern, lcar, loca, ltag, maxp, meta, mort, morx, name, opbd, post,
+ prep, prop, sbix, trak, vhea and vmtx
+
+.. end table list
+
+Other tables are dumped as hexadecimal data.
+
+TrueType fonts use glyph indices (GlyphIDs) to refer to glyphs in most places. While this is fine in binary form, it is really hard to work with for humans. Therefore we use names instead.
+
+The glyph names are either extracted from the ``CFF`` table or the ``post`` table, or are derived from a Unicode ``cmap`` table. In the latter case the Adobe Glyph List is used to calculate names based on Unicode values. If all of these methods fail, names are invented based on GlyphID (eg ``glyph00142``)
+
+It is possible that different glyphs use the same name. If this happens, we force the names to be unique by appending #n to the name (n being an integer number.) The original names are being kept, so this has no influence on a "round tripped" font.
+
+Because the order in which glyphs are stored inside the binary font is important, we maintain an ordered list of glyph names in the font.
+
.. automodule:: fontTools.ttx
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/ufoLib/converters.rst b/Doc/source/ufoLib/converters.rst
index 74aafbf8..2b37e56a 100644
--- a/Doc/source/ufoLib/converters.rst
+++ b/Doc/source/ufoLib/converters.rst
@@ -1,9 +1,9 @@
-.. highlight:: python
-==========
+##########
converters
-==========
+##########
-.. automodule:: ufoLib.converters
+.. automodule:: fontTools.ufoLib.converters
:inherited-members:
:members:
+ :undoc-members:
diff --git a/Doc/source/ufoLib/errors.rst b/Doc/source/ufoLib/errors.rst
new file mode 100644
index 00000000..ba079abd
--- /dev/null
+++ b/Doc/source/ufoLib/errors.rst
@@ -0,0 +1,9 @@
+
+######
+errors
+######
+
+.. automodule:: fontTools.ufoLib.errors
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/ufoLib/filenames.rst b/Doc/source/ufoLib/filenames.rst
index d604c4c5..33a3c1b7 100644
--- a/Doc/source/ufoLib/filenames.rst
+++ b/Doc/source/ufoLib/filenames.rst
@@ -1,9 +1,9 @@
-.. highlight:: python
-=========
+#########
filenames
-=========
+#########
-.. automodule:: ufoLib.filenames
+.. automodule:: fontTools.ufoLib.filenames
:inherited-members:
:members:
+ :undoc-members:
diff --git a/Doc/source/ufoLib/glifLib.rst b/Doc/source/ufoLib/glifLib.rst
index ffe4672f..15bde5ab 100644
--- a/Doc/source/ufoLib/glifLib.rst
+++ b/Doc/source/ufoLib/glifLib.rst
@@ -1,9 +1,9 @@
-.. highlight:: python
-=======
+#######
glifLib
-=======
+#######
-.. automodule:: ufoLib.glifLib
+.. automodule:: fontTools.ufoLib.glifLib
:inherited-members:
:members:
+ :undoc-members:
diff --git a/Doc/source/ufoLib/index.rst b/Doc/source/ufoLib/index.rst
new file mode 100644
index 00000000..514c5c9f
--- /dev/null
+++ b/Doc/source/ufoLib/index.rst
@@ -0,0 +1,22 @@
+
+######
+ufoLib
+######
+
+.. toctree::
+ :maxdepth: 1
+
+ converters
+ errors
+ filenames
+ glifLib
+ kerning
+ plistlib
+ pointpen
+ utils
+ validators
+
+.. automodule:: fontTools.ufoLib
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/ufoLib/kerning.rst b/Doc/source/ufoLib/kerning.rst
new file mode 100644
index 00000000..4d9d0c15
--- /dev/null
+++ b/Doc/source/ufoLib/kerning.rst
@@ -0,0 +1,9 @@
+
+#######
+kerning
+#######
+
+.. automodule:: fontTools.ufoLib.kerning
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/ufoLib/plistlib.rst b/Doc/source/ufoLib/plistlib.rst
new file mode 100644
index 00000000..d639947c
--- /dev/null
+++ b/Doc/source/ufoLib/plistlib.rst
@@ -0,0 +1,9 @@
+
+########
+plistlib
+########
+
+.. automodule:: fontTools.ufoLib.plistlib
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/ufoLib/pointPen.rst b/Doc/source/ufoLib/pointPen.rst
deleted file mode 100644
index 6cb1ca17..00000000
--- a/Doc/source/ufoLib/pointPen.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-.. highlight:: python
-
-========
-pointPen
-========
-
-.. automodule:: ufoLib.pointPen
- :inherited-members:
- :members:
diff --git a/Doc/source/ufoLib/pointpen.rst b/Doc/source/ufoLib/pointpen.rst
new file mode 100644
index 00000000..5fb8c1ca
--- /dev/null
+++ b/Doc/source/ufoLib/pointpen.rst
@@ -0,0 +1,9 @@
+
+########
+pointPen
+########
+
+.. automodule:: fontTools.ufoLib.pointPen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/ufoLib/ufoLib.rst b/Doc/source/ufoLib/ufoLib.rst
deleted file mode 100644
index 3b3b0b1a..00000000
--- a/Doc/source/ufoLib/ufoLib.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-.. highlight:: python
-
-======
-ufoLib
-======
-
-.. automodule:: ufoLib
- :inherited-members:
- :members:
diff --git a/Doc/source/ufoLib/utils.rst b/Doc/source/ufoLib/utils.rst
new file mode 100644
index 00000000..d5ab143e
--- /dev/null
+++ b/Doc/source/ufoLib/utils.rst
@@ -0,0 +1,9 @@
+
+#####
+utils
+#####
+
+.. automodule:: fontTools.ufoLib.utils
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/ufoLib/validators.rst b/Doc/source/ufoLib/validators.rst
new file mode 100644
index 00000000..363d8648
--- /dev/null
+++ b/Doc/source/ufoLib/validators.rst
@@ -0,0 +1,9 @@
+
+##########
+validators
+##########
+
+.. automodule:: fontTools.ufoLib.validators
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/unicode.rst b/Doc/source/unicode.rst
new file mode 100644
index 00000000..65481d58
--- /dev/null
+++ b/Doc/source/unicode.rst
@@ -0,0 +1,8 @@
+#######
+unicode
+#######
+
+.. automodule:: fontTools.unicode
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/unicodedata/Blocks.rst b/Doc/source/unicodedata/Blocks.rst
new file mode 100644
index 00000000..5d01da7e
--- /dev/null
+++ b/Doc/source/unicodedata/Blocks.rst
@@ -0,0 +1,13 @@
+######
+Blocks
+######
+
+.. automodule:: fontTools.unicodedata.Blocks
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+.. data:: fontTools.unicodedata.Blocks.RANGES
+
+.. data:: fontTools.unicodedata.Blocks.VALUES
+
diff --git a/Doc/source/unicodedata/OTTags.rst b/Doc/source/unicodedata/OTTags.rst
new file mode 100644
index 00000000..a436bdc4
--- /dev/null
+++ b/Doc/source/unicodedata/OTTags.rst
@@ -0,0 +1,17 @@
+######
+OTTags
+######
+
+.. automodule:: fontTools.unicodedata.OTTags
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+.. data:: fontTools.unicodedata.OTTags.DEFAULT_SCRIPT
+
+.. data:: fontTools.unicodedata.OTTags.SCRIPT_EXCEPTIONS
+
+.. data:: fontTools.unicodedata.OTTags.NEW_SCRIPT_TAGS
+
+.. data:: fontTools.unicodedata.OTTags.NEW_SCRIPT_TAGS_REVERSED
+
diff --git a/Doc/source/unicodedata/ScriptExtensions.rst b/Doc/source/unicodedata/ScriptExtensions.rst
new file mode 100644
index 00000000..dce2bbc4
--- /dev/null
+++ b/Doc/source/unicodedata/ScriptExtensions.rst
@@ -0,0 +1,12 @@
+################
+ScriptExtensions
+################
+
+.. automodule:: fontTools.unicodedata.ScriptExtensions
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+.. data:: fontTools.unicodedata.ScriptExtensions.RANGES
+
+.. data:: fontTools.unicodedata.ScriptExtensions.VALUES
diff --git a/Doc/source/unicodedata/Scripts.rst b/Doc/source/unicodedata/Scripts.rst
new file mode 100644
index 00000000..2ec6e341
--- /dev/null
+++ b/Doc/source/unicodedata/Scripts.rst
@@ -0,0 +1,16 @@
+#######
+Scripts
+#######
+
+.. automodule:: fontTools.unicodedata.Scripts
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+.. data:: fontTools.unicodedata.Scripts.NAMES
+
+.. data:: fontTools.unicodedata.Scripts.RANGES
+
+.. data:: fontTools.unicodedata.Scripts.VALUES
+
+
diff --git a/Doc/source/unicodedata/index.rst b/Doc/source/unicodedata/index.rst
new file mode 100644
index 00000000..811d65d7
--- /dev/null
+++ b/Doc/source/unicodedata/index.rst
@@ -0,0 +1,16 @@
+###########
+unicodedata
+###########
+
+.. toctree::
+ :maxdepth: 1
+
+ Blocks
+ OTTags
+ ScriptExtensions
+ Scripts
+
+.. automodule:: fontTools.unicodedata
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/varLib/builder.rst b/Doc/source/varLib/builder.rst
new file mode 100644
index 00000000..3da3d32c
--- /dev/null
+++ b/Doc/source/varLib/builder.rst
@@ -0,0 +1,8 @@
+#######
+builder
+#######
+
+.. automodule:: fontTools.varLib.builder
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/varLib/cff.rst b/Doc/source/varLib/cff.rst
new file mode 100644
index 00000000..62e11c73
--- /dev/null
+++ b/Doc/source/varLib/cff.rst
@@ -0,0 +1,8 @@
+###
+cff
+###
+
+.. automodule:: fontTools.varLib.cff
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/varLib/designspace.rst b/Doc/source/varLib/designspace.rst
deleted file mode 100644
index e3bbdf7c..00000000
--- a/Doc/source/varLib/designspace.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-###########
-designspace
-###########
-
-.. automodule:: fontTools.varLib.designspace
- :members:
- :undoc-members:
diff --git a/Doc/source/varLib/errors.rst b/Doc/source/varLib/errors.rst
new file mode 100644
index 00000000..b761854c
--- /dev/null
+++ b/Doc/source/varLib/errors.rst
@@ -0,0 +1,8 @@
+######
+errors
+######
+
+.. automodule:: fontTools.varLib.errors
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/varLib/featureVars.rst b/Doc/source/varLib/featureVars.rst
new file mode 100644
index 00000000..da73560f
--- /dev/null
+++ b/Doc/source/varLib/featureVars.rst
@@ -0,0 +1,8 @@
+###########
+featureVars
+###########
+
+.. automodule:: fontTools.varLib.featureVars
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/varLib/index.rst b/Doc/source/varLib/index.rst
index 0e9f17b8..7b224967 100644
--- a/Doc/source/varLib/index.rst
+++ b/Doc/source/varLib/index.rst
@@ -1,17 +1,114 @@
-######
-varLib
-######
+##################################
+varLib: OpenType Variation Support
+##################################
+
+The ``fontTools.varLib`` package contains a number of classes and routines
+for handling, building and interpolating variable font data. These routines
+rely on a common set of concepts, many of which are equivalent to concepts
+in the OpenType Specification, but some of which are unique to ``varLib``.
+
+Terminology
+-----------
+
+axis
+ "A designer-determined variable in a font face design that can be used to
+ derive multiple, variant designs within a family." (OpenType Specification)
+ An axis has a minimum value, a maximum value and a default value.
+
+designspace
+ The n-dimensional space formed by the font's axes. (OpenType Specification
+ calls this the "design-variation space")
+
+scalar
+ A value which is able to be varied at different points in the designspace:
+ for example, the horizontal advance width of the glyph "a" is a scalar.
+ However, see also *support scalar* below.
+
+default location
+ A point in the designspace whose coordinates are the default value of
+ all axes.
+
+location
+ A point in the designspace, specified as a set of coordinates on one or
+ more axes. In the context of ``varLib``, a location is a dictionary with
+ the keys being the axis tags and the values being the coordinates on the
+ respective axis. A ``varLib`` location dictionary may be "sparse", in the
+ sense that axes defined in the font may be omitted from the location's
+ coordinates, in which case the default value of the axis is assumed.
+ For example, given a font having a ``wght`` axis ranging from 200-1000
+ with default 400, and a ``wdth`` axis ranging 100-300 with default 150,
+ the location ``{"wdth": 200}`` represents the point ``wght=400,wdth=200``.
+
+master
+ The value of a scalar at a given location. **Note that this is a
+ considerably more general concept than the usual type design sense of
+ the term "master".**
+
+normalized location
+ While the range of an axis is determined by its minimum and maximum values
+ as set by the designer, locations are specified internally to the font binary
+ in the range -1 to 1, with 0 being the default, -1 being the minimum and
+ 1 being the maximum. A normalized location is one which is scaled to the
+ range (-1,1) on all of its axes. Note that as the range from minimum to
+ default and from default to maximum on a given axis may differ (for
+ example, given ``wght min=200 default=500 max=1000``, the difference
+ between a normalized location -1 of a normalized location of 0 represents a
+ difference of 300 units while the difference between a normalized location
+ of 0 and a normalized location of 1 represents a difference of 700 units),
+ a location is scaled by a different factor depending on whether it is above
+ or below the axis' default value.
+
+support
+ While designers tend to think in terms of masters - that is, a precise
+ location having a particular value - OpenType Variations specifies the
+ variation of scalars in terms of deltas which are themselves composed of
+ the combined contributions of a set of triangular regions, each having
+ a contribution value of 0 at its minimum value, rising linearly to its
+ full contribution at the *peak* and falling linearly to zero from the
+ peak to the maximum value. The OpenType Specification calls these "regions",
+ while ``varLib`` calls them "supports" (a mathematical term used in real
+ analysis) and expresses them as a dictionary mapping each axis tag to a
+ tuple ``(min, peak, max)``.
+
+box
+ ``varLib`` uses the term "box" to denote the minimum and maximum "corners" of
+ a support, ignoring its peak value.
+
+delta
+ The term "delta" is used in OpenType Variations in two senses. In the
+ more general sense, a delta is the difference between a scalar at a
+ given location and its value at the default location. Additionally, inside
+ the font, variation data is stored as a mapping between supports and deltas.
+ The delta (in the first sense) is computed by summing the product of the
+ delta of each support by a factor representing the support's contribution
+ at this location (see "support scalar" below).
+
+support scalar
+ When interpolating a set of variation data, the support scalar represents
+ the scalar multiplier of the support's contribution at this location. For
+ example, the support scalar will be 1 at the support's peak location, and
+ 0 below its minimum or above its maximum.
+
.. toctree::
:maxdepth: 2
- designspace
+ builder
+ cff
+ errors
+ featureVars
+ instancer
interpolatable
interpolate_layout
+ iup
merger
models
mutator
+ mvar
+ plot
+ varStore
.. automodule:: fontTools.varLib
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/varLib/instancer.rst b/Doc/source/varLib/instancer.rst
new file mode 100644
index 00000000..8776de31
--- /dev/null
+++ b/Doc/source/varLib/instancer.rst
@@ -0,0 +1,8 @@
+#########
+instancer
+#########
+
+.. automodule:: fontTools.varLib.instancer
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/varLib/interpolatable.rst b/Doc/source/varLib/interpolatable.rst
index 969fb614..1120a982 100644
--- a/Doc/source/varLib/interpolatable.rst
+++ b/Doc/source/varLib/interpolatable.rst
@@ -3,5 +3,6 @@ interpolatable
##############
.. automodule:: fontTools.varLib.interpolatable
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/varLib/interpolate_layout.rst b/Doc/source/varLib/interpolate_layout.rst
index 752f748b..a9655b53 100644
--- a/Doc/source/varLib/interpolate_layout.rst
+++ b/Doc/source/varLib/interpolate_layout.rst
@@ -3,5 +3,6 @@ interpolate_layout
##################
.. automodule:: fontTools.varLib.interpolate_layout
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/varLib/iup.rst b/Doc/source/varLib/iup.rst
new file mode 100644
index 00000000..b096788b
--- /dev/null
+++ b/Doc/source/varLib/iup.rst
@@ -0,0 +1,8 @@
+###
+iup
+###
+
+.. automodule:: fontTools.varLib.iup
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/varLib/merger.rst b/Doc/source/varLib/merger.rst
index 37383aa6..cf0a5a1c 100644
--- a/Doc/source/varLib/merger.rst
+++ b/Doc/source/varLib/merger.rst
@@ -3,5 +3,6 @@ merger
######
.. automodule:: fontTools.varLib.merger
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/varLib/models.rst b/Doc/source/varLib/models.rst
index e6c7fa8a..f59f0b84 100644
--- a/Doc/source/varLib/models.rst
+++ b/Doc/source/varLib/models.rst
@@ -3,5 +3,6 @@ models
######
.. automodule:: fontTools.varLib.models
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/varLib/mutator.rst b/Doc/source/varLib/mutator.rst
index e606ab86..fffa8038 100644
--- a/Doc/source/varLib/mutator.rst
+++ b/Doc/source/varLib/mutator.rst
@@ -3,5 +3,6 @@ mutator
#######
.. automodule:: fontTools.varLib.mutator
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Doc/source/varLib/mvar.rst b/Doc/source/varLib/mvar.rst
new file mode 100644
index 00000000..8c59a310
--- /dev/null
+++ b/Doc/source/varLib/mvar.rst
@@ -0,0 +1,10 @@
+####
+mvar
+####
+
+.. automodule:: fontTools.varLib.mvar
+ :inherited-members:
+ :members:
+ :undoc-members:
+
+.. data:: fontTools.varLib.mvar.MVAR_ENTRIES \ No newline at end of file
diff --git a/Doc/source/varLib/plot.rst b/Doc/source/varLib/plot.rst
new file mode 100644
index 00000000..a722a2d6
--- /dev/null
+++ b/Doc/source/varLib/plot.rst
@@ -0,0 +1,8 @@
+####
+plot
+####
+
+.. automodule:: fontTools.varLib.plot
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/varLib/varStore.rst b/Doc/source/varLib/varStore.rst
new file mode 100644
index 00000000..cc91101e
--- /dev/null
+++ b/Doc/source/varLib/varStore.rst
@@ -0,0 +1,8 @@
+########
+varStore
+########
+
+.. automodule:: fontTools.varLib.varStore
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/voltLib.rst b/Doc/source/voltLib.rst
index 5906c4c2..7695db7b 100644
--- a/Doc/source/voltLib.rst
+++ b/Doc/source/voltLib.rst
@@ -3,6 +3,7 @@ voltLib
#######
.. automodule:: fontTools.voltLib
+ :inherited-members:
:members:
:undoc-members:
@@ -10,6 +11,7 @@ ast
---
.. automodule:: fontTools.voltLib.ast
+ :inherited-members:
:members:
:undoc-members:
@@ -17,6 +19,7 @@ error
-----
.. automodule:: fontTools.voltLib.parser
+ :inherited-members:
:members:
:undoc-members:
@@ -24,6 +27,7 @@ lexer
-----
.. automodule:: fontTools.voltLib.lexer
+ :inherited-members:
:members:
:undoc-members:
@@ -31,5 +35,6 @@ parser
------
.. automodule:: fontTools.voltLib.parser
+ :inherited-members:
:members:
:undoc-members:
diff --git a/Icons/FontToolsIconGreenCircle.pdf b/Icons/FontToolsIconGreenCircle.pdf
new file mode 100644
index 00000000..f36817e6
--- /dev/null
+++ b/Icons/FontToolsIconGreenCircle.pdf
Binary files differ
diff --git a/Icons/FontToolsIconGreenCircle.png b/Icons/FontToolsIconGreenCircle.png
new file mode 100644
index 00000000..47d57650
--- /dev/null
+++ b/Icons/FontToolsIconGreenCircle.png
Binary files differ
diff --git a/Icons/FontToolsIconGreenSquare.pdf b/Icons/FontToolsIconGreenSquare.pdf
new file mode 100644
index 00000000..b95057f1
--- /dev/null
+++ b/Icons/FontToolsIconGreenSquare.pdf
Binary files differ
diff --git a/Icons/FontToolsIconGreenSquare.png b/Icons/FontToolsIconGreenSquare.png
new file mode 100644
index 00000000..a6865614
--- /dev/null
+++ b/Icons/FontToolsIconGreenSquare.png
Binary files differ
diff --git a/Icons/FontToolsIconWhiteCircle.pdf b/Icons/FontToolsIconWhiteCircle.pdf
new file mode 100644
index 00000000..c53b81c6
--- /dev/null
+++ b/Icons/FontToolsIconWhiteCircle.pdf
Binary files differ
diff --git a/Icons/FontToolsIconWhiteCircle.png b/Icons/FontToolsIconWhiteCircle.png
new file mode 100644
index 00000000..33601a5c
--- /dev/null
+++ b/Icons/FontToolsIconWhiteCircle.png
Binary files differ
diff --git a/Icons/FontToolsIconWhiteSquare.pdf b/Icons/FontToolsIconWhiteSquare.pdf
new file mode 100644
index 00000000..3692e3ae
--- /dev/null
+++ b/Icons/FontToolsIconWhiteSquare.pdf
Binary files differ
diff --git a/Icons/FontToolsIconWhiteSquare.png b/Icons/FontToolsIconWhiteSquare.png
new file mode 100644
index 00000000..2a2d797e
--- /dev/null
+++ b/Icons/FontToolsIconWhiteSquare.png
Binary files differ
diff --git a/LICENSE.external b/LICENSE.external
index 4abc7d5e..2bc4dab3 100644
--- a/LICENSE.external
+++ b/LICENSE.external
@@ -26,6 +26,10 @@ XITS font project
This Font Software is licensed under the SIL Open Font License, Version 1.1.
+Iosevka
+ Copyright (c) 2015-2020 Belleve Invis (belleve@typeof.net).
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
+
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
@@ -146,3 +150,210 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=====
+
+FontTools includes cu2qu, which is Copyright 2016 Google Inc. All Rights Reserved.
+Licensed under the Apache License, Version 2.0, a copy of which is reproduced below:
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/Lib/fontTools/Android.bp b/Lib/fontTools/Android.bp
index 34ca987f..b7f6139c 100644
--- a/Lib/fontTools/Android.bp
+++ b/Lib/fontTools/Android.bp
@@ -11,17 +11,34 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+// *** THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS. PLEASE
+// CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE
+// DEPENDING ON IT IN YOUR PROJECT. ***
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_fonttools_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ // SPDX-license-identifier-BSD
+ // SPDX-license-identifier-MIT
+ // SPDX-license-identifier-OFL (by exception only)
+ // SPDX-license-identifier-Unicode-DFS
+ // legacy_unencumbered
+ default_applicable_licenses: ["external_fonttools_license"],
+}
+
python_defaults {
name: "fonttools_default",
version: {
py2: {
- enabled: true,
- embedded_launcher: true,
- },
- py3: {
enabled: false,
embedded_launcher: false,
},
+ py3: {
+ enabled: true,
+ embedded_launcher: true,
+ },
},
}
python_library_host {
diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py
index 3d2f79c3..82da9b70 100644
--- a/Lib/fontTools/__init__.py
+++ b/Lib/fontTools/__init__.py
@@ -1,10 +1,8 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import logging
from fontTools.misc.loggingTools import configLogger
log = logging.getLogger(__name__)
-version = __version__ = "3.44.0"
+version = __version__ = "4.22.0"
__all__ = ["version", "log", "configLogger"]
diff --git a/Lib/fontTools/__main__.py b/Lib/fontTools/__main__.py
index 7d45751b..9b978aaa 100644
--- a/Lib/fontTools/__main__.py
+++ b/Lib/fontTools/__main__.py
@@ -1,4 +1,3 @@
-from __future__ import print_function, division, absolute_import
import sys
@@ -6,8 +5,6 @@ def main(args=None):
if args is None:
args = sys.argv[1:]
- # TODO Add help output, --help, etc.
-
# TODO Handle library-wide options. Eg.:
# --unicodedata
# --verbose / other logging stuff
@@ -21,6 +18,10 @@ def main(args=None):
# can be added. Should we just try importing the fonttools
# module first and try without if it fails?
+ if len(sys.argv) < 2:
+ sys.argv.append("help")
+ if sys.argv[1] == "-h" or sys.argv[1] == "--help":
+ sys.argv[1] = "help"
mod = 'fontTools.'+sys.argv[1]
sys.argv[1] = sys.argv[0] + ' ' + sys.argv[1]
del sys.argv[0]
diff --git a/Lib/fontTools/afmLib.py b/Lib/fontTools/afmLib.py
index db01d346..49d99512 100644
--- a/Lib/fontTools/afmLib.py
+++ b/Lib/fontTools/afmLib.py
@@ -1,11 +1,51 @@
-"""Module for reading and writing AFM files."""
+"""Module for reading and writing AFM (Adobe Font Metrics) files.
+
+Note that this has been designed to read in AFM files generated by Fontographer
+and has not been tested on many other files. In particular, it does not
+implement the whole Adobe AFM specification [#f1]_ but, it should read most
+"common" AFM files.
+
+Here is an example of using `afmLib` to read, modify and write an AFM file:
+
+ >>> from fontTools.afmLib import AFM
+ >>> f = AFM("Tests/afmLib/data/TestAFM.afm")
+ >>>
+ >>> # Accessing a pair gets you the kern value
+ >>> f[("V","A")]
+ -60
+ >>>
+ >>> # Accessing a glyph name gets you metrics
+ >>> f["A"]
+ (65, 668, (8, -25, 660, 666))
+ >>> # (charnum, width, bounding box)
+ >>>
+ >>> # Accessing an attribute gets you metadata
+ >>> f.FontName
+ 'TestFont-Regular'
+ >>> f.FamilyName
+ 'TestFont'
+ >>> f.Weight
+ 'Regular'
+ >>> f.XHeight
+ 500
+ >>> f.Ascender
+ 750
+ >>>
+ >>> # Attributes and items can also be set
+ >>> f[("A","V")] = -150 # Tighten kerning
+ >>> f.FontName = "TestFont Squished"
+ >>>
+ >>> # And the font written out again (remove the # in front)
+ >>> #f.write("testfont-squished.afm")
+
+.. rubric:: Footnotes
+
+.. [#f1] `Adobe Technote 5004 <https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5004.AFM_Spec.pdf>`_,
+ Adobe Font Metrics File Format Specification.
+
+"""
-# XXX reads AFM's generated by Fog, not tested with much else.
-# It does not implement the full spec (Adobe Technote 5004, Adobe Font Metrics
-# File Format Specification). Still, it should read most "common" AFM files.
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import re
# every single line starts with a "word"
@@ -98,6 +138,11 @@ class AFM(object):
]
def __init__(self, path=None):
+ """AFM file reader.
+
+ Instantiating an object with a path name will cause the file to be opened,
+ read, and parsed. Alternatively the path can be left unspecified, and a
+ file can be parsed later with the :meth:`read` method."""
self._attrs = {}
self._chars = {}
self._kerning = {}
@@ -108,6 +153,7 @@ class AFM(object):
self.read(path)
def read(self, path):
+ """Opens, reads and parses a file."""
lines = readlines(path)
for line in lines:
if not line.strip():
@@ -190,6 +236,7 @@ class AFM(object):
self._composites[charname] = components
def write(self, path, sep='\r'):
+ """Writes out an AFM font to the given path."""
import time
lines = [ "StartFontMetrics 2.0",
"Comment Generated by afmLib; at %s" % (
@@ -259,24 +306,40 @@ class AFM(object):
writelines(path, lines, sep)
def has_kernpair(self, pair):
+ """Returns `True` if the given glyph pair (specified as a tuple) exists
+ in the kerning dictionary."""
return pair in self._kerning
def kernpairs(self):
+ """Returns a list of all kern pairs in the kerning dictionary."""
return list(self._kerning.keys())
def has_char(self, char):
+ """Returns `True` if the given glyph exists in the font."""
return char in self._chars
def chars(self):
+ """Returns a list of all glyph names in the font."""
return list(self._chars.keys())
def comments(self):
+ """Returns all comments from the file."""
return self._comments
def addComment(self, comment):
+ """Adds a new comment to the file."""
self._comments.append(comment)
def addComposite(self, glyphName, components):
+ """Specifies that the glyph `glyphName` is made up of the given components.
+ The components list should be of the following form::
+
+ [
+ (glyphname, xOffset, yOffset),
+ ...
+ ]
+
+ """
self._composites[glyphName] = components
def __getattr__(self, attr):
diff --git a/Lib/fontTools/agl.py b/Lib/fontTools/agl.py
index ec1a1b07..4f7ff920 100644
--- a/Lib/fontTools/agl.py
+++ b/Lib/fontTools/agl.py
@@ -1,17 +1,4367 @@
# -*- coding: utf-8 -*-
-# The table below is taken from
-# http://www.adobe.com/devnet/opentype/archives/aglfn.txt
+# The tables below are taken from
+# https://github.com/adobe-type-tools/agl-aglfn/raw/4036a9ca80a62f64f9de4f7321a9a045ad0ecfd6/glyphlist.txt
+# and
+# https://github.com/adobe-type-tools/agl-aglfn/raw/4036a9ca80a62f64f9de4f7321a9a045ad0ecfd6/aglfn.txt
+"""
+Interface to the Adobe Glyph List
+
+This module exists to convert glyph names from the Adobe Glyph List
+to their Unicode equivalents. Example usage:
+
+ >>> from fontTools.agl import toUnicode
+ >>> toUnicode("nahiragana")
+ 'な'
+
+It also contains two dictionaries, ``UV2AGL`` and ``AGL2UV``, which map from
+Unicode codepoints to AGL names and vice versa:
+
+ >>> import fontTools
+ >>> fontTools.agl.UV2AGL[ord("?")]
+ 'question'
+ >>> fontTools.agl.AGL2UV["wcircumflex"]
+ 373
+
+This is used by fontTools when it has to construct glyph names for a font which
+doesn't include any (e.g. format 3.0 post tables).
+"""
-from __future__ import (print_function, division, absolute_import,
- unicode_literals)
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tostr
import re
_aglText = """\
# -----------------------------------------------------------
-# Copyright 2003, 2005-2008, 2010 Adobe Systems Incorporated.
-# All rights reserved.
+# Copyright 2002-2019 Adobe (http://www.adobe.com/).
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the
+# following conditions are met:
+#
+# Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# Neither the name of Adobe nor the names of its contributors
+# may be used to endorse or promote products derived from this
+# software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# -----------------------------------------------------------
+# Name: Adobe Glyph List
+# Table version: 2.0
+# Date: September 20, 2002
+# URL: https://github.com/adobe-type-tools/agl-aglfn
+#
+# Format: two semicolon-delimited fields:
+# (1) glyph name--upper/lowercase letters and digits
+# (2) Unicode scalar value--four uppercase hexadecimal digits
+#
+A;0041
+AE;00C6
+AEacute;01FC
+AEmacron;01E2
+AEsmall;F7E6
+Aacute;00C1
+Aacutesmall;F7E1
+Abreve;0102
+Abreveacute;1EAE
+Abrevecyrillic;04D0
+Abrevedotbelow;1EB6
+Abrevegrave;1EB0
+Abrevehookabove;1EB2
+Abrevetilde;1EB4
+Acaron;01CD
+Acircle;24B6
+Acircumflex;00C2
+Acircumflexacute;1EA4
+Acircumflexdotbelow;1EAC
+Acircumflexgrave;1EA6
+Acircumflexhookabove;1EA8
+Acircumflexsmall;F7E2
+Acircumflextilde;1EAA
+Acute;F6C9
+Acutesmall;F7B4
+Acyrillic;0410
+Adblgrave;0200
+Adieresis;00C4
+Adieresiscyrillic;04D2
+Adieresismacron;01DE
+Adieresissmall;F7E4
+Adotbelow;1EA0
+Adotmacron;01E0
+Agrave;00C0
+Agravesmall;F7E0
+Ahookabove;1EA2
+Aiecyrillic;04D4
+Ainvertedbreve;0202
+Alpha;0391
+Alphatonos;0386
+Amacron;0100
+Amonospace;FF21
+Aogonek;0104
+Aring;00C5
+Aringacute;01FA
+Aringbelow;1E00
+Aringsmall;F7E5
+Asmall;F761
+Atilde;00C3
+Atildesmall;F7E3
+Aybarmenian;0531
+B;0042
+Bcircle;24B7
+Bdotaccent;1E02
+Bdotbelow;1E04
+Becyrillic;0411
+Benarmenian;0532
+Beta;0392
+Bhook;0181
+Blinebelow;1E06
+Bmonospace;FF22
+Brevesmall;F6F4
+Bsmall;F762
+Btopbar;0182
+C;0043
+Caarmenian;053E
+Cacute;0106
+Caron;F6CA
+Caronsmall;F6F5
+Ccaron;010C
+Ccedilla;00C7
+Ccedillaacute;1E08
+Ccedillasmall;F7E7
+Ccircle;24B8
+Ccircumflex;0108
+Cdot;010A
+Cdotaccent;010A
+Cedillasmall;F7B8
+Chaarmenian;0549
+Cheabkhasiancyrillic;04BC
+Checyrillic;0427
+Chedescenderabkhasiancyrillic;04BE
+Chedescendercyrillic;04B6
+Chedieresiscyrillic;04F4
+Cheharmenian;0543
+Chekhakassiancyrillic;04CB
+Cheverticalstrokecyrillic;04B8
+Chi;03A7
+Chook;0187
+Circumflexsmall;F6F6
+Cmonospace;FF23
+Coarmenian;0551
+Csmall;F763
+D;0044
+DZ;01F1
+DZcaron;01C4
+Daarmenian;0534
+Dafrican;0189
+Dcaron;010E
+Dcedilla;1E10
+Dcircle;24B9
+Dcircumflexbelow;1E12
+Dcroat;0110
+Ddotaccent;1E0A
+Ddotbelow;1E0C
+Decyrillic;0414
+Deicoptic;03EE
+Delta;2206
+Deltagreek;0394
+Dhook;018A
+Dieresis;F6CB
+DieresisAcute;F6CC
+DieresisGrave;F6CD
+Dieresissmall;F7A8
+Digammagreek;03DC
+Djecyrillic;0402
+Dlinebelow;1E0E
+Dmonospace;FF24
+Dotaccentsmall;F6F7
+Dslash;0110
+Dsmall;F764
+Dtopbar;018B
+Dz;01F2
+Dzcaron;01C5
+Dzeabkhasiancyrillic;04E0
+Dzecyrillic;0405
+Dzhecyrillic;040F
+E;0045
+Eacute;00C9
+Eacutesmall;F7E9
+Ebreve;0114
+Ecaron;011A
+Ecedillabreve;1E1C
+Echarmenian;0535
+Ecircle;24BA
+Ecircumflex;00CA
+Ecircumflexacute;1EBE
+Ecircumflexbelow;1E18
+Ecircumflexdotbelow;1EC6
+Ecircumflexgrave;1EC0
+Ecircumflexhookabove;1EC2
+Ecircumflexsmall;F7EA
+Ecircumflextilde;1EC4
+Ecyrillic;0404
+Edblgrave;0204
+Edieresis;00CB
+Edieresissmall;F7EB
+Edot;0116
+Edotaccent;0116
+Edotbelow;1EB8
+Efcyrillic;0424
+Egrave;00C8
+Egravesmall;F7E8
+Eharmenian;0537
+Ehookabove;1EBA
+Eightroman;2167
+Einvertedbreve;0206
+Eiotifiedcyrillic;0464
+Elcyrillic;041B
+Elevenroman;216A
+Emacron;0112
+Emacronacute;1E16
+Emacrongrave;1E14
+Emcyrillic;041C
+Emonospace;FF25
+Encyrillic;041D
+Endescendercyrillic;04A2
+Eng;014A
+Enghecyrillic;04A4
+Enhookcyrillic;04C7
+Eogonek;0118
+Eopen;0190
+Epsilon;0395
+Epsilontonos;0388
+Ercyrillic;0420
+Ereversed;018E
+Ereversedcyrillic;042D
+Escyrillic;0421
+Esdescendercyrillic;04AA
+Esh;01A9
+Esmall;F765
+Eta;0397
+Etarmenian;0538
+Etatonos;0389
+Eth;00D0
+Ethsmall;F7F0
+Etilde;1EBC
+Etildebelow;1E1A
+Euro;20AC
+Ezh;01B7
+Ezhcaron;01EE
+Ezhreversed;01B8
+F;0046
+Fcircle;24BB
+Fdotaccent;1E1E
+Feharmenian;0556
+Feicoptic;03E4
+Fhook;0191
+Fitacyrillic;0472
+Fiveroman;2164
+Fmonospace;FF26
+Fourroman;2163
+Fsmall;F766
+G;0047
+GBsquare;3387
+Gacute;01F4
+Gamma;0393
+Gammaafrican;0194
+Gangiacoptic;03EA
+Gbreve;011E
+Gcaron;01E6
+Gcedilla;0122
+Gcircle;24BC
+Gcircumflex;011C
+Gcommaaccent;0122
+Gdot;0120
+Gdotaccent;0120
+Gecyrillic;0413
+Ghadarmenian;0542
+Ghemiddlehookcyrillic;0494
+Ghestrokecyrillic;0492
+Gheupturncyrillic;0490
+Ghook;0193
+Gimarmenian;0533
+Gjecyrillic;0403
+Gmacron;1E20
+Gmonospace;FF27
+Grave;F6CE
+Gravesmall;F760
+Gsmall;F767
+Gsmallhook;029B
+Gstroke;01E4
+H;0048
+H18533;25CF
+H18543;25AA
+H18551;25AB
+H22073;25A1
+HPsquare;33CB
+Haabkhasiancyrillic;04A8
+Hadescendercyrillic;04B2
+Hardsigncyrillic;042A
+Hbar;0126
+Hbrevebelow;1E2A
+Hcedilla;1E28
+Hcircle;24BD
+Hcircumflex;0124
+Hdieresis;1E26
+Hdotaccent;1E22
+Hdotbelow;1E24
+Hmonospace;FF28
+Hoarmenian;0540
+Horicoptic;03E8
+Hsmall;F768
+Hungarumlaut;F6CF
+Hungarumlautsmall;F6F8
+Hzsquare;3390
+I;0049
+IAcyrillic;042F
+IJ;0132
+IUcyrillic;042E
+Iacute;00CD
+Iacutesmall;F7ED
+Ibreve;012C
+Icaron;01CF
+Icircle;24BE
+Icircumflex;00CE
+Icircumflexsmall;F7EE
+Icyrillic;0406
+Idblgrave;0208
+Idieresis;00CF
+Idieresisacute;1E2E
+Idieresiscyrillic;04E4
+Idieresissmall;F7EF
+Idot;0130
+Idotaccent;0130
+Idotbelow;1ECA
+Iebrevecyrillic;04D6
+Iecyrillic;0415
+Ifraktur;2111
+Igrave;00CC
+Igravesmall;F7EC
+Ihookabove;1EC8
+Iicyrillic;0418
+Iinvertedbreve;020A
+Iishortcyrillic;0419
+Imacron;012A
+Imacroncyrillic;04E2
+Imonospace;FF29
+Iniarmenian;053B
+Iocyrillic;0401
+Iogonek;012E
+Iota;0399
+Iotaafrican;0196
+Iotadieresis;03AA
+Iotatonos;038A
+Ismall;F769
+Istroke;0197
+Itilde;0128
+Itildebelow;1E2C
+Izhitsacyrillic;0474
+Izhitsadblgravecyrillic;0476
+J;004A
+Jaarmenian;0541
+Jcircle;24BF
+Jcircumflex;0134
+Jecyrillic;0408
+Jheharmenian;054B
+Jmonospace;FF2A
+Jsmall;F76A
+K;004B
+KBsquare;3385
+KKsquare;33CD
+Kabashkircyrillic;04A0
+Kacute;1E30
+Kacyrillic;041A
+Kadescendercyrillic;049A
+Kahookcyrillic;04C3
+Kappa;039A
+Kastrokecyrillic;049E
+Kaverticalstrokecyrillic;049C
+Kcaron;01E8
+Kcedilla;0136
+Kcircle;24C0
+Kcommaaccent;0136
+Kdotbelow;1E32
+Keharmenian;0554
+Kenarmenian;053F
+Khacyrillic;0425
+Kheicoptic;03E6
+Khook;0198
+Kjecyrillic;040C
+Klinebelow;1E34
+Kmonospace;FF2B
+Koppacyrillic;0480
+Koppagreek;03DE
+Ksicyrillic;046E
+Ksmall;F76B
+L;004C
+LJ;01C7
+LL;F6BF
+Lacute;0139
+Lambda;039B
+Lcaron;013D
+Lcedilla;013B
+Lcircle;24C1
+Lcircumflexbelow;1E3C
+Lcommaaccent;013B
+Ldot;013F
+Ldotaccent;013F
+Ldotbelow;1E36
+Ldotbelowmacron;1E38
+Liwnarmenian;053C
+Lj;01C8
+Ljecyrillic;0409
+Llinebelow;1E3A
+Lmonospace;FF2C
+Lslash;0141
+Lslashsmall;F6F9
+Lsmall;F76C
+M;004D
+MBsquare;3386
+Macron;F6D0
+Macronsmall;F7AF
+Macute;1E3E
+Mcircle;24C2
+Mdotaccent;1E40
+Mdotbelow;1E42
+Menarmenian;0544
+Mmonospace;FF2D
+Msmall;F76D
+Mturned;019C
+Mu;039C
+N;004E
+NJ;01CA
+Nacute;0143
+Ncaron;0147
+Ncedilla;0145
+Ncircle;24C3
+Ncircumflexbelow;1E4A
+Ncommaaccent;0145
+Ndotaccent;1E44
+Ndotbelow;1E46
+Nhookleft;019D
+Nineroman;2168
+Nj;01CB
+Njecyrillic;040A
+Nlinebelow;1E48
+Nmonospace;FF2E
+Nowarmenian;0546
+Nsmall;F76E
+Ntilde;00D1
+Ntildesmall;F7F1
+Nu;039D
+O;004F
+OE;0152
+OEsmall;F6FA
+Oacute;00D3
+Oacutesmall;F7F3
+Obarredcyrillic;04E8
+Obarreddieresiscyrillic;04EA
+Obreve;014E
+Ocaron;01D1
+Ocenteredtilde;019F
+Ocircle;24C4
+Ocircumflex;00D4
+Ocircumflexacute;1ED0
+Ocircumflexdotbelow;1ED8
+Ocircumflexgrave;1ED2
+Ocircumflexhookabove;1ED4
+Ocircumflexsmall;F7F4
+Ocircumflextilde;1ED6
+Ocyrillic;041E
+Odblacute;0150
+Odblgrave;020C
+Odieresis;00D6
+Odieresiscyrillic;04E6
+Odieresissmall;F7F6
+Odotbelow;1ECC
+Ogoneksmall;F6FB
+Ograve;00D2
+Ogravesmall;F7F2
+Oharmenian;0555
+Ohm;2126
+Ohookabove;1ECE
+Ohorn;01A0
+Ohornacute;1EDA
+Ohorndotbelow;1EE2
+Ohorngrave;1EDC
+Ohornhookabove;1EDE
+Ohorntilde;1EE0
+Ohungarumlaut;0150
+Oi;01A2
+Oinvertedbreve;020E
+Omacron;014C
+Omacronacute;1E52
+Omacrongrave;1E50
+Omega;2126
+Omegacyrillic;0460
+Omegagreek;03A9
+Omegaroundcyrillic;047A
+Omegatitlocyrillic;047C
+Omegatonos;038F
+Omicron;039F
+Omicrontonos;038C
+Omonospace;FF2F
+Oneroman;2160
+Oogonek;01EA
+Oogonekmacron;01EC
+Oopen;0186
+Oslash;00D8
+Oslashacute;01FE
+Oslashsmall;F7F8
+Osmall;F76F
+Ostrokeacute;01FE
+Otcyrillic;047E
+Otilde;00D5
+Otildeacute;1E4C
+Otildedieresis;1E4E
+Otildesmall;F7F5
+P;0050
+Pacute;1E54
+Pcircle;24C5
+Pdotaccent;1E56
+Pecyrillic;041F
+Peharmenian;054A
+Pemiddlehookcyrillic;04A6
+Phi;03A6
+Phook;01A4
+Pi;03A0
+Piwrarmenian;0553
+Pmonospace;FF30
+Psi;03A8
+Psicyrillic;0470
+Psmall;F770
+Q;0051
+Qcircle;24C6
+Qmonospace;FF31
+Qsmall;F771
+R;0052
+Raarmenian;054C
+Racute;0154
+Rcaron;0158
+Rcedilla;0156
+Rcircle;24C7
+Rcommaaccent;0156
+Rdblgrave;0210
+Rdotaccent;1E58
+Rdotbelow;1E5A
+Rdotbelowmacron;1E5C
+Reharmenian;0550
+Rfraktur;211C
+Rho;03A1
+Ringsmall;F6FC
+Rinvertedbreve;0212
+Rlinebelow;1E5E
+Rmonospace;FF32
+Rsmall;F772
+Rsmallinverted;0281
+Rsmallinvertedsuperior;02B6
+S;0053
+SF010000;250C
+SF020000;2514
+SF030000;2510
+SF040000;2518
+SF050000;253C
+SF060000;252C
+SF070000;2534
+SF080000;251C
+SF090000;2524
+SF100000;2500
+SF110000;2502
+SF190000;2561
+SF200000;2562
+SF210000;2556
+SF220000;2555
+SF230000;2563
+SF240000;2551
+SF250000;2557
+SF260000;255D
+SF270000;255C
+SF280000;255B
+SF360000;255E
+SF370000;255F
+SF380000;255A
+SF390000;2554
+SF400000;2569
+SF410000;2566
+SF420000;2560
+SF430000;2550
+SF440000;256C
+SF450000;2567
+SF460000;2568
+SF470000;2564
+SF480000;2565
+SF490000;2559
+SF500000;2558
+SF510000;2552
+SF520000;2553
+SF530000;256B
+SF540000;256A
+Sacute;015A
+Sacutedotaccent;1E64
+Sampigreek;03E0
+Scaron;0160
+Scarondotaccent;1E66
+Scaronsmall;F6FD
+Scedilla;015E
+Schwa;018F
+Schwacyrillic;04D8
+Schwadieresiscyrillic;04DA
+Scircle;24C8
+Scircumflex;015C
+Scommaaccent;0218
+Sdotaccent;1E60
+Sdotbelow;1E62
+Sdotbelowdotaccent;1E68
+Seharmenian;054D
+Sevenroman;2166
+Shaarmenian;0547
+Shacyrillic;0428
+Shchacyrillic;0429
+Sheicoptic;03E2
+Shhacyrillic;04BA
+Shimacoptic;03EC
+Sigma;03A3
+Sixroman;2165
+Smonospace;FF33
+Softsigncyrillic;042C
+Ssmall;F773
+Stigmagreek;03DA
+T;0054
+Tau;03A4
+Tbar;0166
+Tcaron;0164
+Tcedilla;0162
+Tcircle;24C9
+Tcircumflexbelow;1E70
+Tcommaaccent;0162
+Tdotaccent;1E6A
+Tdotbelow;1E6C
+Tecyrillic;0422
+Tedescendercyrillic;04AC
+Tenroman;2169
+Tetsecyrillic;04B4
+Theta;0398
+Thook;01AC
+Thorn;00DE
+Thornsmall;F7FE
+Threeroman;2162
+Tildesmall;F6FE
+Tiwnarmenian;054F
+Tlinebelow;1E6E
+Tmonospace;FF34
+Toarmenian;0539
+Tonefive;01BC
+Tonesix;0184
+Tonetwo;01A7
+Tretroflexhook;01AE
+Tsecyrillic;0426
+Tshecyrillic;040B
+Tsmall;F774
+Twelveroman;216B
+Tworoman;2161
+U;0055
+Uacute;00DA
+Uacutesmall;F7FA
+Ubreve;016C
+Ucaron;01D3
+Ucircle;24CA
+Ucircumflex;00DB
+Ucircumflexbelow;1E76
+Ucircumflexsmall;F7FB
+Ucyrillic;0423
+Udblacute;0170
+Udblgrave;0214
+Udieresis;00DC
+Udieresisacute;01D7
+Udieresisbelow;1E72
+Udieresiscaron;01D9
+Udieresiscyrillic;04F0
+Udieresisgrave;01DB
+Udieresismacron;01D5
+Udieresissmall;F7FC
+Udotbelow;1EE4
+Ugrave;00D9
+Ugravesmall;F7F9
+Uhookabove;1EE6
+Uhorn;01AF
+Uhornacute;1EE8
+Uhorndotbelow;1EF0
+Uhorngrave;1EEA
+Uhornhookabove;1EEC
+Uhorntilde;1EEE
+Uhungarumlaut;0170
+Uhungarumlautcyrillic;04F2
+Uinvertedbreve;0216
+Ukcyrillic;0478
+Umacron;016A
+Umacroncyrillic;04EE
+Umacrondieresis;1E7A
+Umonospace;FF35
+Uogonek;0172
+Upsilon;03A5
+Upsilon1;03D2
+Upsilonacutehooksymbolgreek;03D3
+Upsilonafrican;01B1
+Upsilondieresis;03AB
+Upsilondieresishooksymbolgreek;03D4
+Upsilonhooksymbol;03D2
+Upsilontonos;038E
+Uring;016E
+Ushortcyrillic;040E
+Usmall;F775
+Ustraightcyrillic;04AE
+Ustraightstrokecyrillic;04B0
+Utilde;0168
+Utildeacute;1E78
+Utildebelow;1E74
+V;0056
+Vcircle;24CB
+Vdotbelow;1E7E
+Vecyrillic;0412
+Vewarmenian;054E
+Vhook;01B2
+Vmonospace;FF36
+Voarmenian;0548
+Vsmall;F776
+Vtilde;1E7C
+W;0057
+Wacute;1E82
+Wcircle;24CC
+Wcircumflex;0174
+Wdieresis;1E84
+Wdotaccent;1E86
+Wdotbelow;1E88
+Wgrave;1E80
+Wmonospace;FF37
+Wsmall;F777
+X;0058
+Xcircle;24CD
+Xdieresis;1E8C
+Xdotaccent;1E8A
+Xeharmenian;053D
+Xi;039E
+Xmonospace;FF38
+Xsmall;F778
+Y;0059
+Yacute;00DD
+Yacutesmall;F7FD
+Yatcyrillic;0462
+Ycircle;24CE
+Ycircumflex;0176
+Ydieresis;0178
+Ydieresissmall;F7FF
+Ydotaccent;1E8E
+Ydotbelow;1EF4
+Yericyrillic;042B
+Yerudieresiscyrillic;04F8
+Ygrave;1EF2
+Yhook;01B3
+Yhookabove;1EF6
+Yiarmenian;0545
+Yicyrillic;0407
+Yiwnarmenian;0552
+Ymonospace;FF39
+Ysmall;F779
+Ytilde;1EF8
+Yusbigcyrillic;046A
+Yusbigiotifiedcyrillic;046C
+Yuslittlecyrillic;0466
+Yuslittleiotifiedcyrillic;0468
+Z;005A
+Zaarmenian;0536
+Zacute;0179
+Zcaron;017D
+Zcaronsmall;F6FF
+Zcircle;24CF
+Zcircumflex;1E90
+Zdot;017B
+Zdotaccent;017B
+Zdotbelow;1E92
+Zecyrillic;0417
+Zedescendercyrillic;0498
+Zedieresiscyrillic;04DE
+Zeta;0396
+Zhearmenian;053A
+Zhebrevecyrillic;04C1
+Zhecyrillic;0416
+Zhedescendercyrillic;0496
+Zhedieresiscyrillic;04DC
+Zlinebelow;1E94
+Zmonospace;FF3A
+Zsmall;F77A
+Zstroke;01B5
+a;0061
+aabengali;0986
+aacute;00E1
+aadeva;0906
+aagujarati;0A86
+aagurmukhi;0A06
+aamatragurmukhi;0A3E
+aarusquare;3303
+aavowelsignbengali;09BE
+aavowelsigndeva;093E
+aavowelsigngujarati;0ABE
+abbreviationmarkarmenian;055F
+abbreviationsigndeva;0970
+abengali;0985
+abopomofo;311A
+abreve;0103
+abreveacute;1EAF
+abrevecyrillic;04D1
+abrevedotbelow;1EB7
+abrevegrave;1EB1
+abrevehookabove;1EB3
+abrevetilde;1EB5
+acaron;01CE
+acircle;24D0
+acircumflex;00E2
+acircumflexacute;1EA5
+acircumflexdotbelow;1EAD
+acircumflexgrave;1EA7
+acircumflexhookabove;1EA9
+acircumflextilde;1EAB
+acute;00B4
+acutebelowcmb;0317
+acutecmb;0301
+acutecomb;0301
+acutedeva;0954
+acutelowmod;02CF
+acutetonecmb;0341
+acyrillic;0430
+adblgrave;0201
+addakgurmukhi;0A71
+adeva;0905
+adieresis;00E4
+adieresiscyrillic;04D3
+adieresismacron;01DF
+adotbelow;1EA1
+adotmacron;01E1
+ae;00E6
+aeacute;01FD
+aekorean;3150
+aemacron;01E3
+afii00208;2015
+afii08941;20A4
+afii10017;0410
+afii10018;0411
+afii10019;0412
+afii10020;0413
+afii10021;0414
+afii10022;0415
+afii10023;0401
+afii10024;0416
+afii10025;0417
+afii10026;0418
+afii10027;0419
+afii10028;041A
+afii10029;041B
+afii10030;041C
+afii10031;041D
+afii10032;041E
+afii10033;041F
+afii10034;0420
+afii10035;0421
+afii10036;0422
+afii10037;0423
+afii10038;0424
+afii10039;0425
+afii10040;0426
+afii10041;0427
+afii10042;0428
+afii10043;0429
+afii10044;042A
+afii10045;042B
+afii10046;042C
+afii10047;042D
+afii10048;042E
+afii10049;042F
+afii10050;0490
+afii10051;0402
+afii10052;0403
+afii10053;0404
+afii10054;0405
+afii10055;0406
+afii10056;0407
+afii10057;0408
+afii10058;0409
+afii10059;040A
+afii10060;040B
+afii10061;040C
+afii10062;040E
+afii10063;F6C4
+afii10064;F6C5
+afii10065;0430
+afii10066;0431
+afii10067;0432
+afii10068;0433
+afii10069;0434
+afii10070;0435
+afii10071;0451
+afii10072;0436
+afii10073;0437
+afii10074;0438
+afii10075;0439
+afii10076;043A
+afii10077;043B
+afii10078;043C
+afii10079;043D
+afii10080;043E
+afii10081;043F
+afii10082;0440
+afii10083;0441
+afii10084;0442
+afii10085;0443
+afii10086;0444
+afii10087;0445
+afii10088;0446
+afii10089;0447
+afii10090;0448
+afii10091;0449
+afii10092;044A
+afii10093;044B
+afii10094;044C
+afii10095;044D
+afii10096;044E
+afii10097;044F
+afii10098;0491
+afii10099;0452
+afii10100;0453
+afii10101;0454
+afii10102;0455
+afii10103;0456
+afii10104;0457
+afii10105;0458
+afii10106;0459
+afii10107;045A
+afii10108;045B
+afii10109;045C
+afii10110;045E
+afii10145;040F
+afii10146;0462
+afii10147;0472
+afii10148;0474
+afii10192;F6C6
+afii10193;045F
+afii10194;0463
+afii10195;0473
+afii10196;0475
+afii10831;F6C7
+afii10832;F6C8
+afii10846;04D9
+afii299;200E
+afii300;200F
+afii301;200D
+afii57381;066A
+afii57388;060C
+afii57392;0660
+afii57393;0661
+afii57394;0662
+afii57395;0663
+afii57396;0664
+afii57397;0665
+afii57398;0666
+afii57399;0667
+afii57400;0668
+afii57401;0669
+afii57403;061B
+afii57407;061F
+afii57409;0621
+afii57410;0622
+afii57411;0623
+afii57412;0624
+afii57413;0625
+afii57414;0626
+afii57415;0627
+afii57416;0628
+afii57417;0629
+afii57418;062A
+afii57419;062B
+afii57420;062C
+afii57421;062D
+afii57422;062E
+afii57423;062F
+afii57424;0630
+afii57425;0631
+afii57426;0632
+afii57427;0633
+afii57428;0634
+afii57429;0635
+afii57430;0636
+afii57431;0637
+afii57432;0638
+afii57433;0639
+afii57434;063A
+afii57440;0640
+afii57441;0641
+afii57442;0642
+afii57443;0643
+afii57444;0644
+afii57445;0645
+afii57446;0646
+afii57448;0648
+afii57449;0649
+afii57450;064A
+afii57451;064B
+afii57452;064C
+afii57453;064D
+afii57454;064E
+afii57455;064F
+afii57456;0650
+afii57457;0651
+afii57458;0652
+afii57470;0647
+afii57505;06A4
+afii57506;067E
+afii57507;0686
+afii57508;0698
+afii57509;06AF
+afii57511;0679
+afii57512;0688
+afii57513;0691
+afii57514;06BA
+afii57519;06D2
+afii57534;06D5
+afii57636;20AA
+afii57645;05BE
+afii57658;05C3
+afii57664;05D0
+afii57665;05D1
+afii57666;05D2
+afii57667;05D3
+afii57668;05D4
+afii57669;05D5
+afii57670;05D6
+afii57671;05D7
+afii57672;05D8
+afii57673;05D9
+afii57674;05DA
+afii57675;05DB
+afii57676;05DC
+afii57677;05DD
+afii57678;05DE
+afii57679;05DF
+afii57680;05E0
+afii57681;05E1
+afii57682;05E2
+afii57683;05E3
+afii57684;05E4
+afii57685;05E5
+afii57686;05E6
+afii57687;05E7
+afii57688;05E8
+afii57689;05E9
+afii57690;05EA
+afii57694;FB2A
+afii57695;FB2B
+afii57700;FB4B
+afii57705;FB1F
+afii57716;05F0
+afii57717;05F1
+afii57718;05F2
+afii57723;FB35
+afii57793;05B4
+afii57794;05B5
+afii57795;05B6
+afii57796;05BB
+afii57797;05B8
+afii57798;05B7
+afii57799;05B0
+afii57800;05B2
+afii57801;05B1
+afii57802;05B3
+afii57803;05C2
+afii57804;05C1
+afii57806;05B9
+afii57807;05BC
+afii57839;05BD
+afii57841;05BF
+afii57842;05C0
+afii57929;02BC
+afii61248;2105
+afii61289;2113
+afii61352;2116
+afii61573;202C
+afii61574;202D
+afii61575;202E
+afii61664;200C
+afii63167;066D
+afii64937;02BD
+agrave;00E0
+agujarati;0A85
+agurmukhi;0A05
+ahiragana;3042
+ahookabove;1EA3
+aibengali;0990
+aibopomofo;311E
+aideva;0910
+aiecyrillic;04D5
+aigujarati;0A90
+aigurmukhi;0A10
+aimatragurmukhi;0A48
+ainarabic;0639
+ainfinalarabic;FECA
+aininitialarabic;FECB
+ainmedialarabic;FECC
+ainvertedbreve;0203
+aivowelsignbengali;09C8
+aivowelsigndeva;0948
+aivowelsigngujarati;0AC8
+akatakana;30A2
+akatakanahalfwidth;FF71
+akorean;314F
+alef;05D0
+alefarabic;0627
+alefdageshhebrew;FB30
+aleffinalarabic;FE8E
+alefhamzaabovearabic;0623
+alefhamzaabovefinalarabic;FE84
+alefhamzabelowarabic;0625
+alefhamzabelowfinalarabic;FE88
+alefhebrew;05D0
+aleflamedhebrew;FB4F
+alefmaddaabovearabic;0622
+alefmaddaabovefinalarabic;FE82
+alefmaksuraarabic;0649
+alefmaksurafinalarabic;FEF0
+alefmaksurainitialarabic;FEF3
+alefmaksuramedialarabic;FEF4
+alefpatahhebrew;FB2E
+alefqamatshebrew;FB2F
+aleph;2135
+allequal;224C
+alpha;03B1
+alphatonos;03AC
+amacron;0101
+amonospace;FF41
+ampersand;0026
+ampersandmonospace;FF06
+ampersandsmall;F726
+amsquare;33C2
+anbopomofo;3122
+angbopomofo;3124
+angkhankhuthai;0E5A
+angle;2220
+anglebracketleft;3008
+anglebracketleftvertical;FE3F
+anglebracketright;3009
+anglebracketrightvertical;FE40
+angleleft;2329
+angleright;232A
+angstrom;212B
+anoteleia;0387
+anudattadeva;0952
+anusvarabengali;0982
+anusvaradeva;0902
+anusvaragujarati;0A82
+aogonek;0105
+apaatosquare;3300
+aparen;249C
+apostrophearmenian;055A
+apostrophemod;02BC
+apple;F8FF
+approaches;2250
+approxequal;2248
+approxequalorimage;2252
+approximatelyequal;2245
+araeaekorean;318E
+araeakorean;318D
+arc;2312
+arighthalfring;1E9A
+aring;00E5
+aringacute;01FB
+aringbelow;1E01
+arrowboth;2194
+arrowdashdown;21E3
+arrowdashleft;21E0
+arrowdashright;21E2
+arrowdashup;21E1
+arrowdblboth;21D4
+arrowdbldown;21D3
+arrowdblleft;21D0
+arrowdblright;21D2
+arrowdblup;21D1
+arrowdown;2193
+arrowdownleft;2199
+arrowdownright;2198
+arrowdownwhite;21E9
+arrowheaddownmod;02C5
+arrowheadleftmod;02C2
+arrowheadrightmod;02C3
+arrowheadupmod;02C4
+arrowhorizex;F8E7
+arrowleft;2190
+arrowleftdbl;21D0
+arrowleftdblstroke;21CD
+arrowleftoverright;21C6
+arrowleftwhite;21E6
+arrowright;2192
+arrowrightdblstroke;21CF
+arrowrightheavy;279E
+arrowrightoverleft;21C4
+arrowrightwhite;21E8
+arrowtableft;21E4
+arrowtabright;21E5
+arrowup;2191
+arrowupdn;2195
+arrowupdnbse;21A8
+arrowupdownbase;21A8
+arrowupleft;2196
+arrowupleftofdown;21C5
+arrowupright;2197
+arrowupwhite;21E7
+arrowvertex;F8E6
+asciicircum;005E
+asciicircummonospace;FF3E
+asciitilde;007E
+asciitildemonospace;FF5E
+ascript;0251
+ascriptturned;0252
+asmallhiragana;3041
+asmallkatakana;30A1
+asmallkatakanahalfwidth;FF67
+asterisk;002A
+asteriskaltonearabic;066D
+asteriskarabic;066D
+asteriskmath;2217
+asteriskmonospace;FF0A
+asterisksmall;FE61
+asterism;2042
+asuperior;F6E9
+asymptoticallyequal;2243
+at;0040
+atilde;00E3
+atmonospace;FF20
+atsmall;FE6B
+aturned;0250
+aubengali;0994
+aubopomofo;3120
+audeva;0914
+augujarati;0A94
+augurmukhi;0A14
+aulengthmarkbengali;09D7
+aumatragurmukhi;0A4C
+auvowelsignbengali;09CC
+auvowelsigndeva;094C
+auvowelsigngujarati;0ACC
+avagrahadeva;093D
+aybarmenian;0561
+ayin;05E2
+ayinaltonehebrew;FB20
+ayinhebrew;05E2
+b;0062
+babengali;09AC
+backslash;005C
+backslashmonospace;FF3C
+badeva;092C
+bagujarati;0AAC
+bagurmukhi;0A2C
+bahiragana;3070
+bahtthai;0E3F
+bakatakana;30D0
+bar;007C
+barmonospace;FF5C
+bbopomofo;3105
+bcircle;24D1
+bdotaccent;1E03
+bdotbelow;1E05
+beamedsixteenthnotes;266C
+because;2235
+becyrillic;0431
+beharabic;0628
+behfinalarabic;FE90
+behinitialarabic;FE91
+behiragana;3079
+behmedialarabic;FE92
+behmeeminitialarabic;FC9F
+behmeemisolatedarabic;FC08
+behnoonfinalarabic;FC6D
+bekatakana;30D9
+benarmenian;0562
+bet;05D1
+beta;03B2
+betasymbolgreek;03D0
+betdagesh;FB31
+betdageshhebrew;FB31
+bethebrew;05D1
+betrafehebrew;FB4C
+bhabengali;09AD
+bhadeva;092D
+bhagujarati;0AAD
+bhagurmukhi;0A2D
+bhook;0253
+bihiragana;3073
+bikatakana;30D3
+bilabialclick;0298
+bindigurmukhi;0A02
+birusquare;3331
+blackcircle;25CF
+blackdiamond;25C6
+blackdownpointingtriangle;25BC
+blackleftpointingpointer;25C4
+blackleftpointingtriangle;25C0
+blacklenticularbracketleft;3010
+blacklenticularbracketleftvertical;FE3B
+blacklenticularbracketright;3011
+blacklenticularbracketrightvertical;FE3C
+blacklowerlefttriangle;25E3
+blacklowerrighttriangle;25E2
+blackrectangle;25AC
+blackrightpointingpointer;25BA
+blackrightpointingtriangle;25B6
+blacksmallsquare;25AA
+blacksmilingface;263B
+blacksquare;25A0
+blackstar;2605
+blackupperlefttriangle;25E4
+blackupperrighttriangle;25E5
+blackuppointingsmalltriangle;25B4
+blackuppointingtriangle;25B2
+blank;2423
+blinebelow;1E07
+block;2588
+bmonospace;FF42
+bobaimaithai;0E1A
+bohiragana;307C
+bokatakana;30DC
+bparen;249D
+bqsquare;33C3
+braceex;F8F4
+braceleft;007B
+braceleftbt;F8F3
+braceleftmid;F8F2
+braceleftmonospace;FF5B
+braceleftsmall;FE5B
+bracelefttp;F8F1
+braceleftvertical;FE37
+braceright;007D
+bracerightbt;F8FE
+bracerightmid;F8FD
+bracerightmonospace;FF5D
+bracerightsmall;FE5C
+bracerighttp;F8FC
+bracerightvertical;FE38
+bracketleft;005B
+bracketleftbt;F8F0
+bracketleftex;F8EF
+bracketleftmonospace;FF3B
+bracketlefttp;F8EE
+bracketright;005D
+bracketrightbt;F8FB
+bracketrightex;F8FA
+bracketrightmonospace;FF3D
+bracketrighttp;F8F9
+breve;02D8
+brevebelowcmb;032E
+brevecmb;0306
+breveinvertedbelowcmb;032F
+breveinvertedcmb;0311
+breveinverteddoublecmb;0361
+bridgebelowcmb;032A
+bridgeinvertedbelowcmb;033A
+brokenbar;00A6
+bstroke;0180
+bsuperior;F6EA
+btopbar;0183
+buhiragana;3076
+bukatakana;30D6
+bullet;2022
+bulletinverse;25D8
+bulletoperator;2219
+bullseye;25CE
+c;0063
+caarmenian;056E
+cabengali;099A
+cacute;0107
+cadeva;091A
+cagujarati;0A9A
+cagurmukhi;0A1A
+calsquare;3388
+candrabindubengali;0981
+candrabinducmb;0310
+candrabindudeva;0901
+candrabindugujarati;0A81
+capslock;21EA
+careof;2105
+caron;02C7
+caronbelowcmb;032C
+caroncmb;030C
+carriagereturn;21B5
+cbopomofo;3118
+ccaron;010D
+ccedilla;00E7
+ccedillaacute;1E09
+ccircle;24D2
+ccircumflex;0109
+ccurl;0255
+cdot;010B
+cdotaccent;010B
+cdsquare;33C5
+cedilla;00B8
+cedillacmb;0327
+cent;00A2
+centigrade;2103
+centinferior;F6DF
+centmonospace;FFE0
+centoldstyle;F7A2
+centsuperior;F6E0
+chaarmenian;0579
+chabengali;099B
+chadeva;091B
+chagujarati;0A9B
+chagurmukhi;0A1B
+chbopomofo;3114
+cheabkhasiancyrillic;04BD
+checkmark;2713
+checyrillic;0447
+chedescenderabkhasiancyrillic;04BF
+chedescendercyrillic;04B7
+chedieresiscyrillic;04F5
+cheharmenian;0573
+chekhakassiancyrillic;04CC
+cheverticalstrokecyrillic;04B9
+chi;03C7
+chieuchacirclekorean;3277
+chieuchaparenkorean;3217
+chieuchcirclekorean;3269
+chieuchkorean;314A
+chieuchparenkorean;3209
+chochangthai;0E0A
+chochanthai;0E08
+chochingthai;0E09
+chochoethai;0E0C
+chook;0188
+cieucacirclekorean;3276
+cieucaparenkorean;3216
+cieuccirclekorean;3268
+cieuckorean;3148
+cieucparenkorean;3208
+cieucuparenkorean;321C
+circle;25CB
+circlemultiply;2297
+circleot;2299
+circleplus;2295
+circlepostalmark;3036
+circlewithlefthalfblack;25D0
+circlewithrighthalfblack;25D1
+circumflex;02C6
+circumflexbelowcmb;032D
+circumflexcmb;0302
+clear;2327
+clickalveolar;01C2
+clickdental;01C0
+clicklateral;01C1
+clickretroflex;01C3
+club;2663
+clubsuitblack;2663
+clubsuitwhite;2667
+cmcubedsquare;33A4
+cmonospace;FF43
+cmsquaredsquare;33A0
+coarmenian;0581
+colon;003A
+colonmonetary;20A1
+colonmonospace;FF1A
+colonsign;20A1
+colonsmall;FE55
+colontriangularhalfmod;02D1
+colontriangularmod;02D0
+comma;002C
+commaabovecmb;0313
+commaaboverightcmb;0315
+commaaccent;F6C3
+commaarabic;060C
+commaarmenian;055D
+commainferior;F6E1
+commamonospace;FF0C
+commareversedabovecmb;0314
+commareversedmod;02BD
+commasmall;FE50
+commasuperior;F6E2
+commaturnedabovecmb;0312
+commaturnedmod;02BB
+compass;263C
+congruent;2245
+contourintegral;222E
+control;2303
+controlACK;0006
+controlBEL;0007
+controlBS;0008
+controlCAN;0018
+controlCR;000D
+controlDC1;0011
+controlDC2;0012
+controlDC3;0013
+controlDC4;0014
+controlDEL;007F
+controlDLE;0010
+controlEM;0019
+controlENQ;0005
+controlEOT;0004
+controlESC;001B
+controlETB;0017
+controlETX;0003
+controlFF;000C
+controlFS;001C
+controlGS;001D
+controlHT;0009
+controlLF;000A
+controlNAK;0015
+controlRS;001E
+controlSI;000F
+controlSO;000E
+controlSOT;0002
+controlSTX;0001
+controlSUB;001A
+controlSYN;0016
+controlUS;001F
+controlVT;000B
+copyright;00A9
+copyrightsans;F8E9
+copyrightserif;F6D9
+cornerbracketleft;300C
+cornerbracketlefthalfwidth;FF62
+cornerbracketleftvertical;FE41
+cornerbracketright;300D
+cornerbracketrighthalfwidth;FF63
+cornerbracketrightvertical;FE42
+corporationsquare;337F
+cosquare;33C7
+coverkgsquare;33C6
+cparen;249E
+cruzeiro;20A2
+cstretched;0297
+curlyand;22CF
+curlyor;22CE
+currency;00A4
+cyrBreve;F6D1
+cyrFlex;F6D2
+cyrbreve;F6D4
+cyrflex;F6D5
+d;0064
+daarmenian;0564
+dabengali;09A6
+dadarabic;0636
+dadeva;0926
+dadfinalarabic;FEBE
+dadinitialarabic;FEBF
+dadmedialarabic;FEC0
+dagesh;05BC
+dageshhebrew;05BC
+dagger;2020
+daggerdbl;2021
+dagujarati;0AA6
+dagurmukhi;0A26
+dahiragana;3060
+dakatakana;30C0
+dalarabic;062F
+dalet;05D3
+daletdagesh;FB33
+daletdageshhebrew;FB33
+dalethatafpatah;05D3 05B2
+dalethatafpatahhebrew;05D3 05B2
+dalethatafsegol;05D3 05B1
+dalethatafsegolhebrew;05D3 05B1
+dalethebrew;05D3
+dalethiriq;05D3 05B4
+dalethiriqhebrew;05D3 05B4
+daletholam;05D3 05B9
+daletholamhebrew;05D3 05B9
+daletpatah;05D3 05B7
+daletpatahhebrew;05D3 05B7
+daletqamats;05D3 05B8
+daletqamatshebrew;05D3 05B8
+daletqubuts;05D3 05BB
+daletqubutshebrew;05D3 05BB
+daletsegol;05D3 05B6
+daletsegolhebrew;05D3 05B6
+daletsheva;05D3 05B0
+daletshevahebrew;05D3 05B0
+dalettsere;05D3 05B5
+dalettserehebrew;05D3 05B5
+dalfinalarabic;FEAA
+dammaarabic;064F
+dammalowarabic;064F
+dammatanaltonearabic;064C
+dammatanarabic;064C
+danda;0964
+dargahebrew;05A7
+dargalefthebrew;05A7
+dasiapneumatacyrilliccmb;0485
+dblGrave;F6D3
+dblanglebracketleft;300A
+dblanglebracketleftvertical;FE3D
+dblanglebracketright;300B
+dblanglebracketrightvertical;FE3E
+dblarchinvertedbelowcmb;032B
+dblarrowleft;21D4
+dblarrowright;21D2
+dbldanda;0965
+dblgrave;F6D6
+dblgravecmb;030F
+dblintegral;222C
+dbllowline;2017
+dbllowlinecmb;0333
+dbloverlinecmb;033F
+dblprimemod;02BA
+dblverticalbar;2016
+dblverticallineabovecmb;030E
+dbopomofo;3109
+dbsquare;33C8
+dcaron;010F
+dcedilla;1E11
+dcircle;24D3
+dcircumflexbelow;1E13
+dcroat;0111
+ddabengali;09A1
+ddadeva;0921
+ddagujarati;0AA1
+ddagurmukhi;0A21
+ddalarabic;0688
+ddalfinalarabic;FB89
+dddhadeva;095C
+ddhabengali;09A2
+ddhadeva;0922
+ddhagujarati;0AA2
+ddhagurmukhi;0A22
+ddotaccent;1E0B
+ddotbelow;1E0D
+decimalseparatorarabic;066B
+decimalseparatorpersian;066B
+decyrillic;0434
+degree;00B0
+dehihebrew;05AD
+dehiragana;3067
+deicoptic;03EF
+dekatakana;30C7
+deleteleft;232B
+deleteright;2326
+delta;03B4
+deltaturned;018D
+denominatorminusonenumeratorbengali;09F8
+dezh;02A4
+dhabengali;09A7
+dhadeva;0927
+dhagujarati;0AA7
+dhagurmukhi;0A27
+dhook;0257
+dialytikatonos;0385
+dialytikatonoscmb;0344
+diamond;2666
+diamondsuitwhite;2662
+dieresis;00A8
+dieresisacute;F6D7
+dieresisbelowcmb;0324
+dieresiscmb;0308
+dieresisgrave;F6D8
+dieresistonos;0385
+dihiragana;3062
+dikatakana;30C2
+dittomark;3003
+divide;00F7
+divides;2223
+divisionslash;2215
+djecyrillic;0452
+dkshade;2593
+dlinebelow;1E0F
+dlsquare;3397
+dmacron;0111
+dmonospace;FF44
+dnblock;2584
+dochadathai;0E0E
+dodekthai;0E14
+dohiragana;3069
+dokatakana;30C9
+dollar;0024
+dollarinferior;F6E3
+dollarmonospace;FF04
+dollaroldstyle;F724
+dollarsmall;FE69
+dollarsuperior;F6E4
+dong;20AB
+dorusquare;3326
+dotaccent;02D9
+dotaccentcmb;0307
+dotbelowcmb;0323
+dotbelowcomb;0323
+dotkatakana;30FB
+dotlessi;0131
+dotlessj;F6BE
+dotlessjstrokehook;0284
+dotmath;22C5
+dottedcircle;25CC
+doubleyodpatah;FB1F
+doubleyodpatahhebrew;FB1F
+downtackbelowcmb;031E
+downtackmod;02D5
+dparen;249F
+dsuperior;F6EB
+dtail;0256
+dtopbar;018C
+duhiragana;3065
+dukatakana;30C5
+dz;01F3
+dzaltone;02A3
+dzcaron;01C6
+dzcurl;02A5
+dzeabkhasiancyrillic;04E1
+dzecyrillic;0455
+dzhecyrillic;045F
+e;0065
+eacute;00E9
+earth;2641
+ebengali;098F
+ebopomofo;311C
+ebreve;0115
+ecandradeva;090D
+ecandragujarati;0A8D
+ecandravowelsigndeva;0945
+ecandravowelsigngujarati;0AC5
+ecaron;011B
+ecedillabreve;1E1D
+echarmenian;0565
+echyiwnarmenian;0587
+ecircle;24D4
+ecircumflex;00EA
+ecircumflexacute;1EBF
+ecircumflexbelow;1E19
+ecircumflexdotbelow;1EC7
+ecircumflexgrave;1EC1
+ecircumflexhookabove;1EC3
+ecircumflextilde;1EC5
+ecyrillic;0454
+edblgrave;0205
+edeva;090F
+edieresis;00EB
+edot;0117
+edotaccent;0117
+edotbelow;1EB9
+eegurmukhi;0A0F
+eematragurmukhi;0A47
+efcyrillic;0444
+egrave;00E8
+egujarati;0A8F
+eharmenian;0567
+ehbopomofo;311D
+ehiragana;3048
+ehookabove;1EBB
+eibopomofo;311F
+eight;0038
+eightarabic;0668
+eightbengali;09EE
+eightcircle;2467
+eightcircleinversesansserif;2791
+eightdeva;096E
+eighteencircle;2471
+eighteenparen;2485
+eighteenperiod;2499
+eightgujarati;0AEE
+eightgurmukhi;0A6E
+eighthackarabic;0668
+eighthangzhou;3028
+eighthnotebeamed;266B
+eightideographicparen;3227
+eightinferior;2088
+eightmonospace;FF18
+eightoldstyle;F738
+eightparen;247B
+eightperiod;248F
+eightpersian;06F8
+eightroman;2177
+eightsuperior;2078
+eightthai;0E58
+einvertedbreve;0207
+eiotifiedcyrillic;0465
+ekatakana;30A8
+ekatakanahalfwidth;FF74
+ekonkargurmukhi;0A74
+ekorean;3154
+elcyrillic;043B
+element;2208
+elevencircle;246A
+elevenparen;247E
+elevenperiod;2492
+elevenroman;217A
+ellipsis;2026
+ellipsisvertical;22EE
+emacron;0113
+emacronacute;1E17
+emacrongrave;1E15
+emcyrillic;043C
+emdash;2014
+emdashvertical;FE31
+emonospace;FF45
+emphasismarkarmenian;055B
+emptyset;2205
+enbopomofo;3123
+encyrillic;043D
+endash;2013
+endashvertical;FE32
+endescendercyrillic;04A3
+eng;014B
+engbopomofo;3125
+enghecyrillic;04A5
+enhookcyrillic;04C8
+enspace;2002
+eogonek;0119
+eokorean;3153
+eopen;025B
+eopenclosed;029A
+eopenreversed;025C
+eopenreversedclosed;025E
+eopenreversedhook;025D
+eparen;24A0
+epsilon;03B5
+epsilontonos;03AD
+equal;003D
+equalmonospace;FF1D
+equalsmall;FE66
+equalsuperior;207C
+equivalence;2261
+erbopomofo;3126
+ercyrillic;0440
+ereversed;0258
+ereversedcyrillic;044D
+escyrillic;0441
+esdescendercyrillic;04AB
+esh;0283
+eshcurl;0286
+eshortdeva;090E
+eshortvowelsigndeva;0946
+eshreversedloop;01AA
+eshsquatreversed;0285
+esmallhiragana;3047
+esmallkatakana;30A7
+esmallkatakanahalfwidth;FF6A
+estimated;212E
+esuperior;F6EC
+eta;03B7
+etarmenian;0568
+etatonos;03AE
+eth;00F0
+etilde;1EBD
+etildebelow;1E1B
+etnahtafoukhhebrew;0591
+etnahtafoukhlefthebrew;0591
+etnahtahebrew;0591
+etnahtalefthebrew;0591
+eturned;01DD
+eukorean;3161
+euro;20AC
+evowelsignbengali;09C7
+evowelsigndeva;0947
+evowelsigngujarati;0AC7
+exclam;0021
+exclamarmenian;055C
+exclamdbl;203C
+exclamdown;00A1
+exclamdownsmall;F7A1
+exclammonospace;FF01
+exclamsmall;F721
+existential;2203
+ezh;0292
+ezhcaron;01EF
+ezhcurl;0293
+ezhreversed;01B9
+ezhtail;01BA
+f;0066
+fadeva;095E
+fagurmukhi;0A5E
+fahrenheit;2109
+fathaarabic;064E
+fathalowarabic;064E
+fathatanarabic;064B
+fbopomofo;3108
+fcircle;24D5
+fdotaccent;1E1F
+feharabic;0641
+feharmenian;0586
+fehfinalarabic;FED2
+fehinitialarabic;FED3
+fehmedialarabic;FED4
+feicoptic;03E5
+female;2640
+ff;FB00
+ffi;FB03
+ffl;FB04
+fi;FB01
+fifteencircle;246E
+fifteenparen;2482
+fifteenperiod;2496
+figuredash;2012
+filledbox;25A0
+filledrect;25AC
+finalkaf;05DA
+finalkafdagesh;FB3A
+finalkafdageshhebrew;FB3A
+finalkafhebrew;05DA
+finalkafqamats;05DA 05B8
+finalkafqamatshebrew;05DA 05B8
+finalkafsheva;05DA 05B0
+finalkafshevahebrew;05DA 05B0
+finalmem;05DD
+finalmemhebrew;05DD
+finalnun;05DF
+finalnunhebrew;05DF
+finalpe;05E3
+finalpehebrew;05E3
+finaltsadi;05E5
+finaltsadihebrew;05E5
+firsttonechinese;02C9
+fisheye;25C9
+fitacyrillic;0473
+five;0035
+fivearabic;0665
+fivebengali;09EB
+fivecircle;2464
+fivecircleinversesansserif;278E
+fivedeva;096B
+fiveeighths;215D
+fivegujarati;0AEB
+fivegurmukhi;0A6B
+fivehackarabic;0665
+fivehangzhou;3025
+fiveideographicparen;3224
+fiveinferior;2085
+fivemonospace;FF15
+fiveoldstyle;F735
+fiveparen;2478
+fiveperiod;248C
+fivepersian;06F5
+fiveroman;2174
+fivesuperior;2075
+fivethai;0E55
+fl;FB02
+florin;0192
+fmonospace;FF46
+fmsquare;3399
+fofanthai;0E1F
+fofathai;0E1D
+fongmanthai;0E4F
+forall;2200
+four;0034
+fourarabic;0664
+fourbengali;09EA
+fourcircle;2463
+fourcircleinversesansserif;278D
+fourdeva;096A
+fourgujarati;0AEA
+fourgurmukhi;0A6A
+fourhackarabic;0664
+fourhangzhou;3024
+fourideographicparen;3223
+fourinferior;2084
+fourmonospace;FF14
+fournumeratorbengali;09F7
+fouroldstyle;F734
+fourparen;2477
+fourperiod;248B
+fourpersian;06F4
+fourroman;2173
+foursuperior;2074
+fourteencircle;246D
+fourteenparen;2481
+fourteenperiod;2495
+fourthai;0E54
+fourthtonechinese;02CB
+fparen;24A1
+fraction;2044
+franc;20A3
+g;0067
+gabengali;0997
+gacute;01F5
+gadeva;0917
+gafarabic;06AF
+gaffinalarabic;FB93
+gafinitialarabic;FB94
+gafmedialarabic;FB95
+gagujarati;0A97
+gagurmukhi;0A17
+gahiragana;304C
+gakatakana;30AC
+gamma;03B3
+gammalatinsmall;0263
+gammasuperior;02E0
+gangiacoptic;03EB
+gbopomofo;310D
+gbreve;011F
+gcaron;01E7
+gcedilla;0123
+gcircle;24D6
+gcircumflex;011D
+gcommaaccent;0123
+gdot;0121
+gdotaccent;0121
+gecyrillic;0433
+gehiragana;3052
+gekatakana;30B2
+geometricallyequal;2251
+gereshaccenthebrew;059C
+gereshhebrew;05F3
+gereshmuqdamhebrew;059D
+germandbls;00DF
+gershayimaccenthebrew;059E
+gershayimhebrew;05F4
+getamark;3013
+ghabengali;0998
+ghadarmenian;0572
+ghadeva;0918
+ghagujarati;0A98
+ghagurmukhi;0A18
+ghainarabic;063A
+ghainfinalarabic;FECE
+ghaininitialarabic;FECF
+ghainmedialarabic;FED0
+ghemiddlehookcyrillic;0495
+ghestrokecyrillic;0493
+gheupturncyrillic;0491
+ghhadeva;095A
+ghhagurmukhi;0A5A
+ghook;0260
+ghzsquare;3393
+gihiragana;304E
+gikatakana;30AE
+gimarmenian;0563
+gimel;05D2
+gimeldagesh;FB32
+gimeldageshhebrew;FB32
+gimelhebrew;05D2
+gjecyrillic;0453
+glottalinvertedstroke;01BE
+glottalstop;0294
+glottalstopinverted;0296
+glottalstopmod;02C0
+glottalstopreversed;0295
+glottalstopreversedmod;02C1
+glottalstopreversedsuperior;02E4
+glottalstopstroke;02A1
+glottalstopstrokereversed;02A2
+gmacron;1E21
+gmonospace;FF47
+gohiragana;3054
+gokatakana;30B4
+gparen;24A2
+gpasquare;33AC
+gradient;2207
+grave;0060
+gravebelowcmb;0316
+gravecmb;0300
+gravecomb;0300
+gravedeva;0953
+gravelowmod;02CE
+gravemonospace;FF40
+gravetonecmb;0340
+greater;003E
+greaterequal;2265
+greaterequalorless;22DB
+greatermonospace;FF1E
+greaterorequivalent;2273
+greaterorless;2277
+greateroverequal;2267
+greatersmall;FE65
+gscript;0261
+gstroke;01E5
+guhiragana;3050
+guillemotleft;00AB
+guillemotright;00BB
+guilsinglleft;2039
+guilsinglright;203A
+gukatakana;30B0
+guramusquare;3318
+gysquare;33C9
+h;0068
+haabkhasiancyrillic;04A9
+haaltonearabic;06C1
+habengali;09B9
+hadescendercyrillic;04B3
+hadeva;0939
+hagujarati;0AB9
+hagurmukhi;0A39
+haharabic;062D
+hahfinalarabic;FEA2
+hahinitialarabic;FEA3
+hahiragana;306F
+hahmedialarabic;FEA4
+haitusquare;332A
+hakatakana;30CF
+hakatakanahalfwidth;FF8A
+halantgurmukhi;0A4D
+hamzaarabic;0621
+hamzadammaarabic;0621 064F
+hamzadammatanarabic;0621 064C
+hamzafathaarabic;0621 064E
+hamzafathatanarabic;0621 064B
+hamzalowarabic;0621
+hamzalowkasraarabic;0621 0650
+hamzalowkasratanarabic;0621 064D
+hamzasukunarabic;0621 0652
+hangulfiller;3164
+hardsigncyrillic;044A
+harpoonleftbarbup;21BC
+harpoonrightbarbup;21C0
+hasquare;33CA
+hatafpatah;05B2
+hatafpatah16;05B2
+hatafpatah23;05B2
+hatafpatah2f;05B2
+hatafpatahhebrew;05B2
+hatafpatahnarrowhebrew;05B2
+hatafpatahquarterhebrew;05B2
+hatafpatahwidehebrew;05B2
+hatafqamats;05B3
+hatafqamats1b;05B3
+hatafqamats28;05B3
+hatafqamats34;05B3
+hatafqamatshebrew;05B3
+hatafqamatsnarrowhebrew;05B3
+hatafqamatsquarterhebrew;05B3
+hatafqamatswidehebrew;05B3
+hatafsegol;05B1
+hatafsegol17;05B1
+hatafsegol24;05B1
+hatafsegol30;05B1
+hatafsegolhebrew;05B1
+hatafsegolnarrowhebrew;05B1
+hatafsegolquarterhebrew;05B1
+hatafsegolwidehebrew;05B1
+hbar;0127
+hbopomofo;310F
+hbrevebelow;1E2B
+hcedilla;1E29
+hcircle;24D7
+hcircumflex;0125
+hdieresis;1E27
+hdotaccent;1E23
+hdotbelow;1E25
+he;05D4
+heart;2665
+heartsuitblack;2665
+heartsuitwhite;2661
+hedagesh;FB34
+hedageshhebrew;FB34
+hehaltonearabic;06C1
+heharabic;0647
+hehebrew;05D4
+hehfinalaltonearabic;FBA7
+hehfinalalttwoarabic;FEEA
+hehfinalarabic;FEEA
+hehhamzaabovefinalarabic;FBA5
+hehhamzaaboveisolatedarabic;FBA4
+hehinitialaltonearabic;FBA8
+hehinitialarabic;FEEB
+hehiragana;3078
+hehmedialaltonearabic;FBA9
+hehmedialarabic;FEEC
+heiseierasquare;337B
+hekatakana;30D8
+hekatakanahalfwidth;FF8D
+hekutaarusquare;3336
+henghook;0267
+herutusquare;3339
+het;05D7
+hethebrew;05D7
+hhook;0266
+hhooksuperior;02B1
+hieuhacirclekorean;327B
+hieuhaparenkorean;321B
+hieuhcirclekorean;326D
+hieuhkorean;314E
+hieuhparenkorean;320D
+hihiragana;3072
+hikatakana;30D2
+hikatakanahalfwidth;FF8B
+hiriq;05B4
+hiriq14;05B4
+hiriq21;05B4
+hiriq2d;05B4
+hiriqhebrew;05B4
+hiriqnarrowhebrew;05B4
+hiriqquarterhebrew;05B4
+hiriqwidehebrew;05B4
+hlinebelow;1E96
+hmonospace;FF48
+hoarmenian;0570
+hohipthai;0E2B
+hohiragana;307B
+hokatakana;30DB
+hokatakanahalfwidth;FF8E
+holam;05B9
+holam19;05B9
+holam26;05B9
+holam32;05B9
+holamhebrew;05B9
+holamnarrowhebrew;05B9
+holamquarterhebrew;05B9
+holamwidehebrew;05B9
+honokhukthai;0E2E
+hookabovecomb;0309
+hookcmb;0309
+hookpalatalizedbelowcmb;0321
+hookretroflexbelowcmb;0322
+hoonsquare;3342
+horicoptic;03E9
+horizontalbar;2015
+horncmb;031B
+hotsprings;2668
+house;2302
+hparen;24A3
+hsuperior;02B0
+hturned;0265
+huhiragana;3075
+huiitosquare;3333
+hukatakana;30D5
+hukatakanahalfwidth;FF8C
+hungarumlaut;02DD
+hungarumlautcmb;030B
+hv;0195
+hyphen;002D
+hypheninferior;F6E5
+hyphenmonospace;FF0D
+hyphensmall;FE63
+hyphensuperior;F6E6
+hyphentwo;2010
+i;0069
+iacute;00ED
+iacyrillic;044F
+ibengali;0987
+ibopomofo;3127
+ibreve;012D
+icaron;01D0
+icircle;24D8
+icircumflex;00EE
+icyrillic;0456
+idblgrave;0209
+ideographearthcircle;328F
+ideographfirecircle;328B
+ideographicallianceparen;323F
+ideographiccallparen;323A
+ideographiccentrecircle;32A5
+ideographicclose;3006
+ideographiccomma;3001
+ideographiccommaleft;FF64
+ideographiccongratulationparen;3237
+ideographiccorrectcircle;32A3
+ideographicearthparen;322F
+ideographicenterpriseparen;323D
+ideographicexcellentcircle;329D
+ideographicfestivalparen;3240
+ideographicfinancialcircle;3296
+ideographicfinancialparen;3236
+ideographicfireparen;322B
+ideographichaveparen;3232
+ideographichighcircle;32A4
+ideographiciterationmark;3005
+ideographiclaborcircle;3298
+ideographiclaborparen;3238
+ideographicleftcircle;32A7
+ideographiclowcircle;32A6
+ideographicmedicinecircle;32A9
+ideographicmetalparen;322E
+ideographicmoonparen;322A
+ideographicnameparen;3234
+ideographicperiod;3002
+ideographicprintcircle;329E
+ideographicreachparen;3243
+ideographicrepresentparen;3239
+ideographicresourceparen;323E
+ideographicrightcircle;32A8
+ideographicsecretcircle;3299
+ideographicselfparen;3242
+ideographicsocietyparen;3233
+ideographicspace;3000
+ideographicspecialparen;3235
+ideographicstockparen;3231
+ideographicstudyparen;323B
+ideographicsunparen;3230
+ideographicsuperviseparen;323C
+ideographicwaterparen;322C
+ideographicwoodparen;322D
+ideographiczero;3007
+ideographmetalcircle;328E
+ideographmooncircle;328A
+ideographnamecircle;3294
+ideographsuncircle;3290
+ideographwatercircle;328C
+ideographwoodcircle;328D
+ideva;0907
+idieresis;00EF
+idieresisacute;1E2F
+idieresiscyrillic;04E5
+idotbelow;1ECB
+iebrevecyrillic;04D7
+iecyrillic;0435
+ieungacirclekorean;3275
+ieungaparenkorean;3215
+ieungcirclekorean;3267
+ieungkorean;3147
+ieungparenkorean;3207
+igrave;00EC
+igujarati;0A87
+igurmukhi;0A07
+ihiragana;3044
+ihookabove;1EC9
+iibengali;0988
+iicyrillic;0438
+iideva;0908
+iigujarati;0A88
+iigurmukhi;0A08
+iimatragurmukhi;0A40
+iinvertedbreve;020B
+iishortcyrillic;0439
+iivowelsignbengali;09C0
+iivowelsigndeva;0940
+iivowelsigngujarati;0AC0
+ij;0133
+ikatakana;30A4
+ikatakanahalfwidth;FF72
+ikorean;3163
+ilde;02DC
+iluyhebrew;05AC
+imacron;012B
+imacroncyrillic;04E3
+imageorapproximatelyequal;2253
+imatragurmukhi;0A3F
+imonospace;FF49
+increment;2206
+infinity;221E
+iniarmenian;056B
+integral;222B
+integralbottom;2321
+integralbt;2321
+integralex;F8F5
+integraltop;2320
+integraltp;2320
+intersection;2229
+intisquare;3305
+invbullet;25D8
+invcircle;25D9
+invsmileface;263B
+iocyrillic;0451
+iogonek;012F
+iota;03B9
+iotadieresis;03CA
+iotadieresistonos;0390
+iotalatin;0269
+iotatonos;03AF
+iparen;24A4
+irigurmukhi;0A72
+ismallhiragana;3043
+ismallkatakana;30A3
+ismallkatakanahalfwidth;FF68
+issharbengali;09FA
+istroke;0268
+isuperior;F6ED
+iterationhiragana;309D
+iterationkatakana;30FD
+itilde;0129
+itildebelow;1E2D
+iubopomofo;3129
+iucyrillic;044E
+ivowelsignbengali;09BF
+ivowelsigndeva;093F
+ivowelsigngujarati;0ABF
+izhitsacyrillic;0475
+izhitsadblgravecyrillic;0477
+j;006A
+jaarmenian;0571
+jabengali;099C
+jadeva;091C
+jagujarati;0A9C
+jagurmukhi;0A1C
+jbopomofo;3110
+jcaron;01F0
+jcircle;24D9
+jcircumflex;0135
+jcrossedtail;029D
+jdotlessstroke;025F
+jecyrillic;0458
+jeemarabic;062C
+jeemfinalarabic;FE9E
+jeeminitialarabic;FE9F
+jeemmedialarabic;FEA0
+jeharabic;0698
+jehfinalarabic;FB8B
+jhabengali;099D
+jhadeva;091D
+jhagujarati;0A9D
+jhagurmukhi;0A1D
+jheharmenian;057B
+jis;3004
+jmonospace;FF4A
+jparen;24A5
+jsuperior;02B2
+k;006B
+kabashkircyrillic;04A1
+kabengali;0995
+kacute;1E31
+kacyrillic;043A
+kadescendercyrillic;049B
+kadeva;0915
+kaf;05DB
+kafarabic;0643
+kafdagesh;FB3B
+kafdageshhebrew;FB3B
+kaffinalarabic;FEDA
+kafhebrew;05DB
+kafinitialarabic;FEDB
+kafmedialarabic;FEDC
+kafrafehebrew;FB4D
+kagujarati;0A95
+kagurmukhi;0A15
+kahiragana;304B
+kahookcyrillic;04C4
+kakatakana;30AB
+kakatakanahalfwidth;FF76
+kappa;03BA
+kappasymbolgreek;03F0
+kapyeounmieumkorean;3171
+kapyeounphieuphkorean;3184
+kapyeounpieupkorean;3178
+kapyeounssangpieupkorean;3179
+karoriisquare;330D
+kashidaautoarabic;0640
+kashidaautonosidebearingarabic;0640
+kasmallkatakana;30F5
+kasquare;3384
+kasraarabic;0650
+kasratanarabic;064D
+kastrokecyrillic;049F
+katahiraprolongmarkhalfwidth;FF70
+kaverticalstrokecyrillic;049D
+kbopomofo;310E
+kcalsquare;3389
+kcaron;01E9
+kcedilla;0137
+kcircle;24DA
+kcommaaccent;0137
+kdotbelow;1E33
+keharmenian;0584
+kehiragana;3051
+kekatakana;30B1
+kekatakanahalfwidth;FF79
+kenarmenian;056F
+kesmallkatakana;30F6
+kgreenlandic;0138
+khabengali;0996
+khacyrillic;0445
+khadeva;0916
+khagujarati;0A96
+khagurmukhi;0A16
+khaharabic;062E
+khahfinalarabic;FEA6
+khahinitialarabic;FEA7
+khahmedialarabic;FEA8
+kheicoptic;03E7
+khhadeva;0959
+khhagurmukhi;0A59
+khieukhacirclekorean;3278
+khieukhaparenkorean;3218
+khieukhcirclekorean;326A
+khieukhkorean;314B
+khieukhparenkorean;320A
+khokhaithai;0E02
+khokhonthai;0E05
+khokhuatthai;0E03
+khokhwaithai;0E04
+khomutthai;0E5B
+khook;0199
+khorakhangthai;0E06
+khzsquare;3391
+kihiragana;304D
+kikatakana;30AD
+kikatakanahalfwidth;FF77
+kiroguramusquare;3315
+kiromeetorusquare;3316
+kirosquare;3314
+kiyeokacirclekorean;326E
+kiyeokaparenkorean;320E
+kiyeokcirclekorean;3260
+kiyeokkorean;3131
+kiyeokparenkorean;3200
+kiyeoksioskorean;3133
+kjecyrillic;045C
+klinebelow;1E35
+klsquare;3398
+kmcubedsquare;33A6
+kmonospace;FF4B
+kmsquaredsquare;33A2
+kohiragana;3053
+kohmsquare;33C0
+kokaithai;0E01
+kokatakana;30B3
+kokatakanahalfwidth;FF7A
+kooposquare;331E
+koppacyrillic;0481
+koreanstandardsymbol;327F
+koroniscmb;0343
+kparen;24A6
+kpasquare;33AA
+ksicyrillic;046F
+ktsquare;33CF
+kturned;029E
+kuhiragana;304F
+kukatakana;30AF
+kukatakanahalfwidth;FF78
+kvsquare;33B8
+kwsquare;33BE
+l;006C
+labengali;09B2
+lacute;013A
+ladeva;0932
+lagujarati;0AB2
+lagurmukhi;0A32
+lakkhangyaothai;0E45
+lamaleffinalarabic;FEFC
+lamalefhamzaabovefinalarabic;FEF8
+lamalefhamzaaboveisolatedarabic;FEF7
+lamalefhamzabelowfinalarabic;FEFA
+lamalefhamzabelowisolatedarabic;FEF9
+lamalefisolatedarabic;FEFB
+lamalefmaddaabovefinalarabic;FEF6
+lamalefmaddaaboveisolatedarabic;FEF5
+lamarabic;0644
+lambda;03BB
+lambdastroke;019B
+lamed;05DC
+lameddagesh;FB3C
+lameddageshhebrew;FB3C
+lamedhebrew;05DC
+lamedholam;05DC 05B9
+lamedholamdagesh;05DC 05B9 05BC
+lamedholamdageshhebrew;05DC 05B9 05BC
+lamedholamhebrew;05DC 05B9
+lamfinalarabic;FEDE
+lamhahinitialarabic;FCCA
+laminitialarabic;FEDF
+lamjeeminitialarabic;FCC9
+lamkhahinitialarabic;FCCB
+lamlamhehisolatedarabic;FDF2
+lammedialarabic;FEE0
+lammeemhahinitialarabic;FD88
+lammeeminitialarabic;FCCC
+lammeemjeeminitialarabic;FEDF FEE4 FEA0
+lammeemkhahinitialarabic;FEDF FEE4 FEA8
+largecircle;25EF
+lbar;019A
+lbelt;026C
+lbopomofo;310C
+lcaron;013E
+lcedilla;013C
+lcircle;24DB
+lcircumflexbelow;1E3D
+lcommaaccent;013C
+ldot;0140
+ldotaccent;0140
+ldotbelow;1E37
+ldotbelowmacron;1E39
+leftangleabovecmb;031A
+lefttackbelowcmb;0318
+less;003C
+lessequal;2264
+lessequalorgreater;22DA
+lessmonospace;FF1C
+lessorequivalent;2272
+lessorgreater;2276
+lessoverequal;2266
+lesssmall;FE64
+lezh;026E
+lfblock;258C
+lhookretroflex;026D
+lira;20A4
+liwnarmenian;056C
+lj;01C9
+ljecyrillic;0459
+ll;F6C0
+lladeva;0933
+llagujarati;0AB3
+llinebelow;1E3B
+llladeva;0934
+llvocalicbengali;09E1
+llvocalicdeva;0961
+llvocalicvowelsignbengali;09E3
+llvocalicvowelsigndeva;0963
+lmiddletilde;026B
+lmonospace;FF4C
+lmsquare;33D0
+lochulathai;0E2C
+logicaland;2227
+logicalnot;00AC
+logicalnotreversed;2310
+logicalor;2228
+lolingthai;0E25
+longs;017F
+lowlinecenterline;FE4E
+lowlinecmb;0332
+lowlinedashed;FE4D
+lozenge;25CA
+lparen;24A7
+lslash;0142
+lsquare;2113
+lsuperior;F6EE
+ltshade;2591
+luthai;0E26
+lvocalicbengali;098C
+lvocalicdeva;090C
+lvocalicvowelsignbengali;09E2
+lvocalicvowelsigndeva;0962
+lxsquare;33D3
+m;006D
+mabengali;09AE
+macron;00AF
+macronbelowcmb;0331
+macroncmb;0304
+macronlowmod;02CD
+macronmonospace;FFE3
+macute;1E3F
+madeva;092E
+magujarati;0AAE
+magurmukhi;0A2E
+mahapakhhebrew;05A4
+mahapakhlefthebrew;05A4
+mahiragana;307E
+maichattawalowleftthai;F895
+maichattawalowrightthai;F894
+maichattawathai;0E4B
+maichattawaupperleftthai;F893
+maieklowleftthai;F88C
+maieklowrightthai;F88B
+maiekthai;0E48
+maiekupperleftthai;F88A
+maihanakatleftthai;F884
+maihanakatthai;0E31
+maitaikhuleftthai;F889
+maitaikhuthai;0E47
+maitholowleftthai;F88F
+maitholowrightthai;F88E
+maithothai;0E49
+maithoupperleftthai;F88D
+maitrilowleftthai;F892
+maitrilowrightthai;F891
+maitrithai;0E4A
+maitriupperleftthai;F890
+maiyamokthai;0E46
+makatakana;30DE
+makatakanahalfwidth;FF8F
+male;2642
+mansyonsquare;3347
+maqafhebrew;05BE
+mars;2642
+masoracirclehebrew;05AF
+masquare;3383
+mbopomofo;3107
+mbsquare;33D4
+mcircle;24DC
+mcubedsquare;33A5
+mdotaccent;1E41
+mdotbelow;1E43
+meemarabic;0645
+meemfinalarabic;FEE2
+meeminitialarabic;FEE3
+meemmedialarabic;FEE4
+meemmeeminitialarabic;FCD1
+meemmeemisolatedarabic;FC48
+meetorusquare;334D
+mehiragana;3081
+meizierasquare;337E
+mekatakana;30E1
+mekatakanahalfwidth;FF92
+mem;05DE
+memdagesh;FB3E
+memdageshhebrew;FB3E
+memhebrew;05DE
+menarmenian;0574
+merkhahebrew;05A5
+merkhakefulahebrew;05A6
+merkhakefulalefthebrew;05A6
+merkhalefthebrew;05A5
+mhook;0271
+mhzsquare;3392
+middledotkatakanahalfwidth;FF65
+middot;00B7
+mieumacirclekorean;3272
+mieumaparenkorean;3212
+mieumcirclekorean;3264
+mieumkorean;3141
+mieumpansioskorean;3170
+mieumparenkorean;3204
+mieumpieupkorean;316E
+mieumsioskorean;316F
+mihiragana;307F
+mikatakana;30DF
+mikatakanahalfwidth;FF90
+minus;2212
+minusbelowcmb;0320
+minuscircle;2296
+minusmod;02D7
+minusplus;2213
+minute;2032
+miribaarusquare;334A
+mirisquare;3349
+mlonglegturned;0270
+mlsquare;3396
+mmcubedsquare;33A3
+mmonospace;FF4D
+mmsquaredsquare;339F
+mohiragana;3082
+mohmsquare;33C1
+mokatakana;30E2
+mokatakanahalfwidth;FF93
+molsquare;33D6
+momathai;0E21
+moverssquare;33A7
+moverssquaredsquare;33A8
+mparen;24A8
+mpasquare;33AB
+mssquare;33B3
+msuperior;F6EF
+mturned;026F
+mu;00B5
+mu1;00B5
+muasquare;3382
+muchgreater;226B
+muchless;226A
+mufsquare;338C
+mugreek;03BC
+mugsquare;338D
+muhiragana;3080
+mukatakana;30E0
+mukatakanahalfwidth;FF91
+mulsquare;3395
+multiply;00D7
+mumsquare;339B
+munahhebrew;05A3
+munahlefthebrew;05A3
+musicalnote;266A
+musicalnotedbl;266B
+musicflatsign;266D
+musicsharpsign;266F
+mussquare;33B2
+muvsquare;33B6
+muwsquare;33BC
+mvmegasquare;33B9
+mvsquare;33B7
+mwmegasquare;33BF
+mwsquare;33BD
+n;006E
+nabengali;09A8
+nabla;2207
+nacute;0144
+nadeva;0928
+nagujarati;0AA8
+nagurmukhi;0A28
+nahiragana;306A
+nakatakana;30CA
+nakatakanahalfwidth;FF85
+napostrophe;0149
+nasquare;3381
+nbopomofo;310B
+nbspace;00A0
+ncaron;0148
+ncedilla;0146
+ncircle;24DD
+ncircumflexbelow;1E4B
+ncommaaccent;0146
+ndotaccent;1E45
+ndotbelow;1E47
+nehiragana;306D
+nekatakana;30CD
+nekatakanahalfwidth;FF88
+newsheqelsign;20AA
+nfsquare;338B
+ngabengali;0999
+ngadeva;0919
+ngagujarati;0A99
+ngagurmukhi;0A19
+ngonguthai;0E07
+nhiragana;3093
+nhookleft;0272
+nhookretroflex;0273
+nieunacirclekorean;326F
+nieunaparenkorean;320F
+nieuncieuckorean;3135
+nieuncirclekorean;3261
+nieunhieuhkorean;3136
+nieunkorean;3134
+nieunpansioskorean;3168
+nieunparenkorean;3201
+nieunsioskorean;3167
+nieuntikeutkorean;3166
+nihiragana;306B
+nikatakana;30CB
+nikatakanahalfwidth;FF86
+nikhahitleftthai;F899
+nikhahitthai;0E4D
+nine;0039
+ninearabic;0669
+ninebengali;09EF
+ninecircle;2468
+ninecircleinversesansserif;2792
+ninedeva;096F
+ninegujarati;0AEF
+ninegurmukhi;0A6F
+ninehackarabic;0669
+ninehangzhou;3029
+nineideographicparen;3228
+nineinferior;2089
+ninemonospace;FF19
+nineoldstyle;F739
+nineparen;247C
+nineperiod;2490
+ninepersian;06F9
+nineroman;2178
+ninesuperior;2079
+nineteencircle;2472
+nineteenparen;2486
+nineteenperiod;249A
+ninethai;0E59
+nj;01CC
+njecyrillic;045A
+nkatakana;30F3
+nkatakanahalfwidth;FF9D
+nlegrightlong;019E
+nlinebelow;1E49
+nmonospace;FF4E
+nmsquare;339A
+nnabengali;09A3
+nnadeva;0923
+nnagujarati;0AA3
+nnagurmukhi;0A23
+nnnadeva;0929
+nohiragana;306E
+nokatakana;30CE
+nokatakanahalfwidth;FF89
+nonbreakingspace;00A0
+nonenthai;0E13
+nonuthai;0E19
+noonarabic;0646
+noonfinalarabic;FEE6
+noonghunnaarabic;06BA
+noonghunnafinalarabic;FB9F
+noonhehinitialarabic;FEE7 FEEC
+nooninitialarabic;FEE7
+noonjeeminitialarabic;FCD2
+noonjeemisolatedarabic;FC4B
+noonmedialarabic;FEE8
+noonmeeminitialarabic;FCD5
+noonmeemisolatedarabic;FC4E
+noonnoonfinalarabic;FC8D
+notcontains;220C
+notelement;2209
+notelementof;2209
+notequal;2260
+notgreater;226F
+notgreaternorequal;2271
+notgreaternorless;2279
+notidentical;2262
+notless;226E
+notlessnorequal;2270
+notparallel;2226
+notprecedes;2280
+notsubset;2284
+notsucceeds;2281
+notsuperset;2285
+nowarmenian;0576
+nparen;24A9
+nssquare;33B1
+nsuperior;207F
+ntilde;00F1
+nu;03BD
+nuhiragana;306C
+nukatakana;30CC
+nukatakanahalfwidth;FF87
+nuktabengali;09BC
+nuktadeva;093C
+nuktagujarati;0ABC
+nuktagurmukhi;0A3C
+numbersign;0023
+numbersignmonospace;FF03
+numbersignsmall;FE5F
+numeralsigngreek;0374
+numeralsignlowergreek;0375
+numero;2116
+nun;05E0
+nundagesh;FB40
+nundageshhebrew;FB40
+nunhebrew;05E0
+nvsquare;33B5
+nwsquare;33BB
+nyabengali;099E
+nyadeva;091E
+nyagujarati;0A9E
+nyagurmukhi;0A1E
+o;006F
+oacute;00F3
+oangthai;0E2D
+obarred;0275
+obarredcyrillic;04E9
+obarreddieresiscyrillic;04EB
+obengali;0993
+obopomofo;311B
+obreve;014F
+ocandradeva;0911
+ocandragujarati;0A91
+ocandravowelsigndeva;0949
+ocandravowelsigngujarati;0AC9
+ocaron;01D2
+ocircle;24DE
+ocircumflex;00F4
+ocircumflexacute;1ED1
+ocircumflexdotbelow;1ED9
+ocircumflexgrave;1ED3
+ocircumflexhookabove;1ED5
+ocircumflextilde;1ED7
+ocyrillic;043E
+odblacute;0151
+odblgrave;020D
+odeva;0913
+odieresis;00F6
+odieresiscyrillic;04E7
+odotbelow;1ECD
+oe;0153
+oekorean;315A
+ogonek;02DB
+ogonekcmb;0328
+ograve;00F2
+ogujarati;0A93
+oharmenian;0585
+ohiragana;304A
+ohookabove;1ECF
+ohorn;01A1
+ohornacute;1EDB
+ohorndotbelow;1EE3
+ohorngrave;1EDD
+ohornhookabove;1EDF
+ohorntilde;1EE1
+ohungarumlaut;0151
+oi;01A3
+oinvertedbreve;020F
+okatakana;30AA
+okatakanahalfwidth;FF75
+okorean;3157
+olehebrew;05AB
+omacron;014D
+omacronacute;1E53
+omacrongrave;1E51
+omdeva;0950
+omega;03C9
+omega1;03D6
+omegacyrillic;0461
+omegalatinclosed;0277
+omegaroundcyrillic;047B
+omegatitlocyrillic;047D
+omegatonos;03CE
+omgujarati;0AD0
+omicron;03BF
+omicrontonos;03CC
+omonospace;FF4F
+one;0031
+onearabic;0661
+onebengali;09E7
+onecircle;2460
+onecircleinversesansserif;278A
+onedeva;0967
+onedotenleader;2024
+oneeighth;215B
+onefitted;F6DC
+onegujarati;0AE7
+onegurmukhi;0A67
+onehackarabic;0661
+onehalf;00BD
+onehangzhou;3021
+oneideographicparen;3220
+oneinferior;2081
+onemonospace;FF11
+onenumeratorbengali;09F4
+oneoldstyle;F731
+oneparen;2474
+oneperiod;2488
+onepersian;06F1
+onequarter;00BC
+oneroman;2170
+onesuperior;00B9
+onethai;0E51
+onethird;2153
+oogonek;01EB
+oogonekmacron;01ED
+oogurmukhi;0A13
+oomatragurmukhi;0A4B
+oopen;0254
+oparen;24AA
+openbullet;25E6
+option;2325
+ordfeminine;00AA
+ordmasculine;00BA
+orthogonal;221F
+oshortdeva;0912
+oshortvowelsigndeva;094A
+oslash;00F8
+oslashacute;01FF
+osmallhiragana;3049
+osmallkatakana;30A9
+osmallkatakanahalfwidth;FF6B
+ostrokeacute;01FF
+osuperior;F6F0
+otcyrillic;047F
+otilde;00F5
+otildeacute;1E4D
+otildedieresis;1E4F
+oubopomofo;3121
+overline;203E
+overlinecenterline;FE4A
+overlinecmb;0305
+overlinedashed;FE49
+overlinedblwavy;FE4C
+overlinewavy;FE4B
+overscore;00AF
+ovowelsignbengali;09CB
+ovowelsigndeva;094B
+ovowelsigngujarati;0ACB
+p;0070
+paampssquare;3380
+paasentosquare;332B
+pabengali;09AA
+pacute;1E55
+padeva;092A
+pagedown;21DF
+pageup;21DE
+pagujarati;0AAA
+pagurmukhi;0A2A
+pahiragana;3071
+paiyannoithai;0E2F
+pakatakana;30D1
+palatalizationcyrilliccmb;0484
+palochkacyrillic;04C0
+pansioskorean;317F
+paragraph;00B6
+parallel;2225
+parenleft;0028
+parenleftaltonearabic;FD3E
+parenleftbt;F8ED
+parenleftex;F8EC
+parenleftinferior;208D
+parenleftmonospace;FF08
+parenleftsmall;FE59
+parenleftsuperior;207D
+parenlefttp;F8EB
+parenleftvertical;FE35
+parenright;0029
+parenrightaltonearabic;FD3F
+parenrightbt;F8F8
+parenrightex;F8F7
+parenrightinferior;208E
+parenrightmonospace;FF09
+parenrightsmall;FE5A
+parenrightsuperior;207E
+parenrighttp;F8F6
+parenrightvertical;FE36
+partialdiff;2202
+paseqhebrew;05C0
+pashtahebrew;0599
+pasquare;33A9
+patah;05B7
+patah11;05B7
+patah1d;05B7
+patah2a;05B7
+patahhebrew;05B7
+patahnarrowhebrew;05B7
+patahquarterhebrew;05B7
+patahwidehebrew;05B7
+pazerhebrew;05A1
+pbopomofo;3106
+pcircle;24DF
+pdotaccent;1E57
+pe;05E4
+pecyrillic;043F
+pedagesh;FB44
+pedageshhebrew;FB44
+peezisquare;333B
+pefinaldageshhebrew;FB43
+peharabic;067E
+peharmenian;057A
+pehebrew;05E4
+pehfinalarabic;FB57
+pehinitialarabic;FB58
+pehiragana;307A
+pehmedialarabic;FB59
+pekatakana;30DA
+pemiddlehookcyrillic;04A7
+perafehebrew;FB4E
+percent;0025
+percentarabic;066A
+percentmonospace;FF05
+percentsmall;FE6A
+period;002E
+periodarmenian;0589
+periodcentered;00B7
+periodhalfwidth;FF61
+periodinferior;F6E7
+periodmonospace;FF0E
+periodsmall;FE52
+periodsuperior;F6E8
+perispomenigreekcmb;0342
+perpendicular;22A5
+perthousand;2030
+peseta;20A7
+pfsquare;338A
+phabengali;09AB
+phadeva;092B
+phagujarati;0AAB
+phagurmukhi;0A2B
+phi;03C6
+phi1;03D5
+phieuphacirclekorean;327A
+phieuphaparenkorean;321A
+phieuphcirclekorean;326C
+phieuphkorean;314D
+phieuphparenkorean;320C
+philatin;0278
+phinthuthai;0E3A
+phisymbolgreek;03D5
+phook;01A5
+phophanthai;0E1E
+phophungthai;0E1C
+phosamphaothai;0E20
+pi;03C0
+pieupacirclekorean;3273
+pieupaparenkorean;3213
+pieupcieuckorean;3176
+pieupcirclekorean;3265
+pieupkiyeokkorean;3172
+pieupkorean;3142
+pieupparenkorean;3205
+pieupsioskiyeokkorean;3174
+pieupsioskorean;3144
+pieupsiostikeutkorean;3175
+pieupthieuthkorean;3177
+pieuptikeutkorean;3173
+pihiragana;3074
+pikatakana;30D4
+pisymbolgreek;03D6
+piwrarmenian;0583
+plus;002B
+plusbelowcmb;031F
+pluscircle;2295
+plusminus;00B1
+plusmod;02D6
+plusmonospace;FF0B
+plussmall;FE62
+plussuperior;207A
+pmonospace;FF50
+pmsquare;33D8
+pohiragana;307D
+pointingindexdownwhite;261F
+pointingindexleftwhite;261C
+pointingindexrightwhite;261E
+pointingindexupwhite;261D
+pokatakana;30DD
+poplathai;0E1B
+postalmark;3012
+postalmarkface;3020
+pparen;24AB
+precedes;227A
+prescription;211E
+primemod;02B9
+primereversed;2035
+product;220F
+projective;2305
+prolongedkana;30FC
+propellor;2318
+propersubset;2282
+propersuperset;2283
+proportion;2237
+proportional;221D
+psi;03C8
+psicyrillic;0471
+psilipneumatacyrilliccmb;0486
+pssquare;33B0
+puhiragana;3077
+pukatakana;30D7
+pvsquare;33B4
+pwsquare;33BA
+q;0071
+qadeva;0958
+qadmahebrew;05A8
+qafarabic;0642
+qaffinalarabic;FED6
+qafinitialarabic;FED7
+qafmedialarabic;FED8
+qamats;05B8
+qamats10;05B8
+qamats1a;05B8
+qamats1c;05B8
+qamats27;05B8
+qamats29;05B8
+qamats33;05B8
+qamatsde;05B8
+qamatshebrew;05B8
+qamatsnarrowhebrew;05B8
+qamatsqatanhebrew;05B8
+qamatsqatannarrowhebrew;05B8
+qamatsqatanquarterhebrew;05B8
+qamatsqatanwidehebrew;05B8
+qamatsquarterhebrew;05B8
+qamatswidehebrew;05B8
+qarneyparahebrew;059F
+qbopomofo;3111
+qcircle;24E0
+qhook;02A0
+qmonospace;FF51
+qof;05E7
+qofdagesh;FB47
+qofdageshhebrew;FB47
+qofhatafpatah;05E7 05B2
+qofhatafpatahhebrew;05E7 05B2
+qofhatafsegol;05E7 05B1
+qofhatafsegolhebrew;05E7 05B1
+qofhebrew;05E7
+qofhiriq;05E7 05B4
+qofhiriqhebrew;05E7 05B4
+qofholam;05E7 05B9
+qofholamhebrew;05E7 05B9
+qofpatah;05E7 05B7
+qofpatahhebrew;05E7 05B7
+qofqamats;05E7 05B8
+qofqamatshebrew;05E7 05B8
+qofqubuts;05E7 05BB
+qofqubutshebrew;05E7 05BB
+qofsegol;05E7 05B6
+qofsegolhebrew;05E7 05B6
+qofsheva;05E7 05B0
+qofshevahebrew;05E7 05B0
+qoftsere;05E7 05B5
+qoftserehebrew;05E7 05B5
+qparen;24AC
+quarternote;2669
+qubuts;05BB
+qubuts18;05BB
+qubuts25;05BB
+qubuts31;05BB
+qubutshebrew;05BB
+qubutsnarrowhebrew;05BB
+qubutsquarterhebrew;05BB
+qubutswidehebrew;05BB
+question;003F
+questionarabic;061F
+questionarmenian;055E
+questiondown;00BF
+questiondownsmall;F7BF
+questiongreek;037E
+questionmonospace;FF1F
+questionsmall;F73F
+quotedbl;0022
+quotedblbase;201E
+quotedblleft;201C
+quotedblmonospace;FF02
+quotedblprime;301E
+quotedblprimereversed;301D
+quotedblright;201D
+quoteleft;2018
+quoteleftreversed;201B
+quotereversed;201B
+quoteright;2019
+quoterightn;0149
+quotesinglbase;201A
+quotesingle;0027
+quotesinglemonospace;FF07
+r;0072
+raarmenian;057C
+rabengali;09B0
+racute;0155
+radeva;0930
+radical;221A
+radicalex;F8E5
+radoverssquare;33AE
+radoverssquaredsquare;33AF
+radsquare;33AD
+rafe;05BF
+rafehebrew;05BF
+ragujarati;0AB0
+ragurmukhi;0A30
+rahiragana;3089
+rakatakana;30E9
+rakatakanahalfwidth;FF97
+ralowerdiagonalbengali;09F1
+ramiddlediagonalbengali;09F0
+ramshorn;0264
+ratio;2236
+rbopomofo;3116
+rcaron;0159
+rcedilla;0157
+rcircle;24E1
+rcommaaccent;0157
+rdblgrave;0211
+rdotaccent;1E59
+rdotbelow;1E5B
+rdotbelowmacron;1E5D
+referencemark;203B
+reflexsubset;2286
+reflexsuperset;2287
+registered;00AE
+registersans;F8E8
+registerserif;F6DA
+reharabic;0631
+reharmenian;0580
+rehfinalarabic;FEAE
+rehiragana;308C
+rehyehaleflamarabic;0631 FEF3 FE8E 0644
+rekatakana;30EC
+rekatakanahalfwidth;FF9A
+resh;05E8
+reshdageshhebrew;FB48
+reshhatafpatah;05E8 05B2
+reshhatafpatahhebrew;05E8 05B2
+reshhatafsegol;05E8 05B1
+reshhatafsegolhebrew;05E8 05B1
+reshhebrew;05E8
+reshhiriq;05E8 05B4
+reshhiriqhebrew;05E8 05B4
+reshholam;05E8 05B9
+reshholamhebrew;05E8 05B9
+reshpatah;05E8 05B7
+reshpatahhebrew;05E8 05B7
+reshqamats;05E8 05B8
+reshqamatshebrew;05E8 05B8
+reshqubuts;05E8 05BB
+reshqubutshebrew;05E8 05BB
+reshsegol;05E8 05B6
+reshsegolhebrew;05E8 05B6
+reshsheva;05E8 05B0
+reshshevahebrew;05E8 05B0
+reshtsere;05E8 05B5
+reshtserehebrew;05E8 05B5
+reversedtilde;223D
+reviahebrew;0597
+reviamugrashhebrew;0597
+revlogicalnot;2310
+rfishhook;027E
+rfishhookreversed;027F
+rhabengali;09DD
+rhadeva;095D
+rho;03C1
+rhook;027D
+rhookturned;027B
+rhookturnedsuperior;02B5
+rhosymbolgreek;03F1
+rhotichookmod;02DE
+rieulacirclekorean;3271
+rieulaparenkorean;3211
+rieulcirclekorean;3263
+rieulhieuhkorean;3140
+rieulkiyeokkorean;313A
+rieulkiyeoksioskorean;3169
+rieulkorean;3139
+rieulmieumkorean;313B
+rieulpansioskorean;316C
+rieulparenkorean;3203
+rieulphieuphkorean;313F
+rieulpieupkorean;313C
+rieulpieupsioskorean;316B
+rieulsioskorean;313D
+rieulthieuthkorean;313E
+rieultikeutkorean;316A
+rieulyeorinhieuhkorean;316D
+rightangle;221F
+righttackbelowcmb;0319
+righttriangle;22BF
+rihiragana;308A
+rikatakana;30EA
+rikatakanahalfwidth;FF98
+ring;02DA
+ringbelowcmb;0325
+ringcmb;030A
+ringhalfleft;02BF
+ringhalfleftarmenian;0559
+ringhalfleftbelowcmb;031C
+ringhalfleftcentered;02D3
+ringhalfright;02BE
+ringhalfrightbelowcmb;0339
+ringhalfrightcentered;02D2
+rinvertedbreve;0213
+rittorusquare;3351
+rlinebelow;1E5F
+rlongleg;027C
+rlonglegturned;027A
+rmonospace;FF52
+rohiragana;308D
+rokatakana;30ED
+rokatakanahalfwidth;FF9B
+roruathai;0E23
+rparen;24AD
+rrabengali;09DC
+rradeva;0931
+rragurmukhi;0A5C
+rreharabic;0691
+rrehfinalarabic;FB8D
+rrvocalicbengali;09E0
+rrvocalicdeva;0960
+rrvocalicgujarati;0AE0
+rrvocalicvowelsignbengali;09C4
+rrvocalicvowelsigndeva;0944
+rrvocalicvowelsigngujarati;0AC4
+rsuperior;F6F1
+rtblock;2590
+rturned;0279
+rturnedsuperior;02B4
+ruhiragana;308B
+rukatakana;30EB
+rukatakanahalfwidth;FF99
+rupeemarkbengali;09F2
+rupeesignbengali;09F3
+rupiah;F6DD
+ruthai;0E24
+rvocalicbengali;098B
+rvocalicdeva;090B
+rvocalicgujarati;0A8B
+rvocalicvowelsignbengali;09C3
+rvocalicvowelsigndeva;0943
+rvocalicvowelsigngujarati;0AC3
+s;0073
+sabengali;09B8
+sacute;015B
+sacutedotaccent;1E65
+sadarabic;0635
+sadeva;0938
+sadfinalarabic;FEBA
+sadinitialarabic;FEBB
+sadmedialarabic;FEBC
+sagujarati;0AB8
+sagurmukhi;0A38
+sahiragana;3055
+sakatakana;30B5
+sakatakanahalfwidth;FF7B
+sallallahoualayhewasallamarabic;FDFA
+samekh;05E1
+samekhdagesh;FB41
+samekhdageshhebrew;FB41
+samekhhebrew;05E1
+saraaathai;0E32
+saraaethai;0E41
+saraaimaimalaithai;0E44
+saraaimaimuanthai;0E43
+saraamthai;0E33
+saraathai;0E30
+saraethai;0E40
+saraiileftthai;F886
+saraiithai;0E35
+saraileftthai;F885
+saraithai;0E34
+saraothai;0E42
+saraueeleftthai;F888
+saraueethai;0E37
+saraueleftthai;F887
+sarauethai;0E36
+sarauthai;0E38
+sarauuthai;0E39
+sbopomofo;3119
+scaron;0161
+scarondotaccent;1E67
+scedilla;015F
+schwa;0259
+schwacyrillic;04D9
+schwadieresiscyrillic;04DB
+schwahook;025A
+scircle;24E2
+scircumflex;015D
+scommaaccent;0219
+sdotaccent;1E61
+sdotbelow;1E63
+sdotbelowdotaccent;1E69
+seagullbelowcmb;033C
+second;2033
+secondtonechinese;02CA
+section;00A7
+seenarabic;0633
+seenfinalarabic;FEB2
+seeninitialarabic;FEB3
+seenmedialarabic;FEB4
+segol;05B6
+segol13;05B6
+segol1f;05B6
+segol2c;05B6
+segolhebrew;05B6
+segolnarrowhebrew;05B6
+segolquarterhebrew;05B6
+segoltahebrew;0592
+segolwidehebrew;05B6
+seharmenian;057D
+sehiragana;305B
+sekatakana;30BB
+sekatakanahalfwidth;FF7E
+semicolon;003B
+semicolonarabic;061B
+semicolonmonospace;FF1B
+semicolonsmall;FE54
+semivoicedmarkkana;309C
+semivoicedmarkkanahalfwidth;FF9F
+sentisquare;3322
+sentosquare;3323
+seven;0037
+sevenarabic;0667
+sevenbengali;09ED
+sevencircle;2466
+sevencircleinversesansserif;2790
+sevendeva;096D
+seveneighths;215E
+sevengujarati;0AED
+sevengurmukhi;0A6D
+sevenhackarabic;0667
+sevenhangzhou;3027
+sevenideographicparen;3226
+seveninferior;2087
+sevenmonospace;FF17
+sevenoldstyle;F737
+sevenparen;247A
+sevenperiod;248E
+sevenpersian;06F7
+sevenroman;2176
+sevensuperior;2077
+seventeencircle;2470
+seventeenparen;2484
+seventeenperiod;2498
+seventhai;0E57
+sfthyphen;00AD
+shaarmenian;0577
+shabengali;09B6
+shacyrillic;0448
+shaddaarabic;0651
+shaddadammaarabic;FC61
+shaddadammatanarabic;FC5E
+shaddafathaarabic;FC60
+shaddafathatanarabic;0651 064B
+shaddakasraarabic;FC62
+shaddakasratanarabic;FC5F
+shade;2592
+shadedark;2593
+shadelight;2591
+shademedium;2592
+shadeva;0936
+shagujarati;0AB6
+shagurmukhi;0A36
+shalshelethebrew;0593
+shbopomofo;3115
+shchacyrillic;0449
+sheenarabic;0634
+sheenfinalarabic;FEB6
+sheeninitialarabic;FEB7
+sheenmedialarabic;FEB8
+sheicoptic;03E3
+sheqel;20AA
+sheqelhebrew;20AA
+sheva;05B0
+sheva115;05B0
+sheva15;05B0
+sheva22;05B0
+sheva2e;05B0
+shevahebrew;05B0
+shevanarrowhebrew;05B0
+shevaquarterhebrew;05B0
+shevawidehebrew;05B0
+shhacyrillic;04BB
+shimacoptic;03ED
+shin;05E9
+shindagesh;FB49
+shindageshhebrew;FB49
+shindageshshindot;FB2C
+shindageshshindothebrew;FB2C
+shindageshsindot;FB2D
+shindageshsindothebrew;FB2D
+shindothebrew;05C1
+shinhebrew;05E9
+shinshindot;FB2A
+shinshindothebrew;FB2A
+shinsindot;FB2B
+shinsindothebrew;FB2B
+shook;0282
+sigma;03C3
+sigma1;03C2
+sigmafinal;03C2
+sigmalunatesymbolgreek;03F2
+sihiragana;3057
+sikatakana;30B7
+sikatakanahalfwidth;FF7C
+siluqhebrew;05BD
+siluqlefthebrew;05BD
+similar;223C
+sindothebrew;05C2
+siosacirclekorean;3274
+siosaparenkorean;3214
+sioscieuckorean;317E
+sioscirclekorean;3266
+sioskiyeokkorean;317A
+sioskorean;3145
+siosnieunkorean;317B
+siosparenkorean;3206
+siospieupkorean;317D
+siostikeutkorean;317C
+six;0036
+sixarabic;0666
+sixbengali;09EC
+sixcircle;2465
+sixcircleinversesansserif;278F
+sixdeva;096C
+sixgujarati;0AEC
+sixgurmukhi;0A6C
+sixhackarabic;0666
+sixhangzhou;3026
+sixideographicparen;3225
+sixinferior;2086
+sixmonospace;FF16
+sixoldstyle;F736
+sixparen;2479
+sixperiod;248D
+sixpersian;06F6
+sixroman;2175
+sixsuperior;2076
+sixteencircle;246F
+sixteencurrencydenominatorbengali;09F9
+sixteenparen;2483
+sixteenperiod;2497
+sixthai;0E56
+slash;002F
+slashmonospace;FF0F
+slong;017F
+slongdotaccent;1E9B
+smileface;263A
+smonospace;FF53
+sofpasuqhebrew;05C3
+softhyphen;00AD
+softsigncyrillic;044C
+sohiragana;305D
+sokatakana;30BD
+sokatakanahalfwidth;FF7F
+soliduslongoverlaycmb;0338
+solidusshortoverlaycmb;0337
+sorusithai;0E29
+sosalathai;0E28
+sosothai;0E0B
+sosuathai;0E2A
+space;0020
+spacehackarabic;0020
+spade;2660
+spadesuitblack;2660
+spadesuitwhite;2664
+sparen;24AE
+squarebelowcmb;033B
+squarecc;33C4
+squarecm;339D
+squarediagonalcrosshatchfill;25A9
+squarehorizontalfill;25A4
+squarekg;338F
+squarekm;339E
+squarekmcapital;33CE
+squareln;33D1
+squarelog;33D2
+squaremg;338E
+squaremil;33D5
+squaremm;339C
+squaremsquared;33A1
+squareorthogonalcrosshatchfill;25A6
+squareupperlefttolowerrightfill;25A7
+squareupperrighttolowerleftfill;25A8
+squareverticalfill;25A5
+squarewhitewithsmallblack;25A3
+srsquare;33DB
+ssabengali;09B7
+ssadeva;0937
+ssagujarati;0AB7
+ssangcieuckorean;3149
+ssanghieuhkorean;3185
+ssangieungkorean;3180
+ssangkiyeokkorean;3132
+ssangnieunkorean;3165
+ssangpieupkorean;3143
+ssangsioskorean;3146
+ssangtikeutkorean;3138
+ssuperior;F6F2
+sterling;00A3
+sterlingmonospace;FFE1
+strokelongoverlaycmb;0336
+strokeshortoverlaycmb;0335
+subset;2282
+subsetnotequal;228A
+subsetorequal;2286
+succeeds;227B
+suchthat;220B
+suhiragana;3059
+sukatakana;30B9
+sukatakanahalfwidth;FF7D
+sukunarabic;0652
+summation;2211
+sun;263C
+superset;2283
+supersetnotequal;228B
+supersetorequal;2287
+svsquare;33DC
+syouwaerasquare;337C
+t;0074
+tabengali;09A4
+tackdown;22A4
+tackleft;22A3
+tadeva;0924
+tagujarati;0AA4
+tagurmukhi;0A24
+taharabic;0637
+tahfinalarabic;FEC2
+tahinitialarabic;FEC3
+tahiragana;305F
+tahmedialarabic;FEC4
+taisyouerasquare;337D
+takatakana;30BF
+takatakanahalfwidth;FF80
+tatweelarabic;0640
+tau;03C4
+tav;05EA
+tavdages;FB4A
+tavdagesh;FB4A
+tavdageshhebrew;FB4A
+tavhebrew;05EA
+tbar;0167
+tbopomofo;310A
+tcaron;0165
+tccurl;02A8
+tcedilla;0163
+tcheharabic;0686
+tchehfinalarabic;FB7B
+tchehinitialarabic;FB7C
+tchehmedialarabic;FB7D
+tchehmeeminitialarabic;FB7C FEE4
+tcircle;24E3
+tcircumflexbelow;1E71
+tcommaaccent;0163
+tdieresis;1E97
+tdotaccent;1E6B
+tdotbelow;1E6D
+tecyrillic;0442
+tedescendercyrillic;04AD
+teharabic;062A
+tehfinalarabic;FE96
+tehhahinitialarabic;FCA2
+tehhahisolatedarabic;FC0C
+tehinitialarabic;FE97
+tehiragana;3066
+tehjeeminitialarabic;FCA1
+tehjeemisolatedarabic;FC0B
+tehmarbutaarabic;0629
+tehmarbutafinalarabic;FE94
+tehmedialarabic;FE98
+tehmeeminitialarabic;FCA4
+tehmeemisolatedarabic;FC0E
+tehnoonfinalarabic;FC73
+tekatakana;30C6
+tekatakanahalfwidth;FF83
+telephone;2121
+telephoneblack;260E
+telishagedolahebrew;05A0
+telishaqetanahebrew;05A9
+tencircle;2469
+tenideographicparen;3229
+tenparen;247D
+tenperiod;2491
+tenroman;2179
+tesh;02A7
+tet;05D8
+tetdagesh;FB38
+tetdageshhebrew;FB38
+tethebrew;05D8
+tetsecyrillic;04B5
+tevirhebrew;059B
+tevirlefthebrew;059B
+thabengali;09A5
+thadeva;0925
+thagujarati;0AA5
+thagurmukhi;0A25
+thalarabic;0630
+thalfinalarabic;FEAC
+thanthakhatlowleftthai;F898
+thanthakhatlowrightthai;F897
+thanthakhatthai;0E4C
+thanthakhatupperleftthai;F896
+theharabic;062B
+thehfinalarabic;FE9A
+thehinitialarabic;FE9B
+thehmedialarabic;FE9C
+thereexists;2203
+therefore;2234
+theta;03B8
+theta1;03D1
+thetasymbolgreek;03D1
+thieuthacirclekorean;3279
+thieuthaparenkorean;3219
+thieuthcirclekorean;326B
+thieuthkorean;314C
+thieuthparenkorean;320B
+thirteencircle;246C
+thirteenparen;2480
+thirteenperiod;2494
+thonangmonthothai;0E11
+thook;01AD
+thophuthaothai;0E12
+thorn;00FE
+thothahanthai;0E17
+thothanthai;0E10
+thothongthai;0E18
+thothungthai;0E16
+thousandcyrillic;0482
+thousandsseparatorarabic;066C
+thousandsseparatorpersian;066C
+three;0033
+threearabic;0663
+threebengali;09E9
+threecircle;2462
+threecircleinversesansserif;278C
+threedeva;0969
+threeeighths;215C
+threegujarati;0AE9
+threegurmukhi;0A69
+threehackarabic;0663
+threehangzhou;3023
+threeideographicparen;3222
+threeinferior;2083
+threemonospace;FF13
+threenumeratorbengali;09F6
+threeoldstyle;F733
+threeparen;2476
+threeperiod;248A
+threepersian;06F3
+threequarters;00BE
+threequartersemdash;F6DE
+threeroman;2172
+threesuperior;00B3
+threethai;0E53
+thzsquare;3394
+tihiragana;3061
+tikatakana;30C1
+tikatakanahalfwidth;FF81
+tikeutacirclekorean;3270
+tikeutaparenkorean;3210
+tikeutcirclekorean;3262
+tikeutkorean;3137
+tikeutparenkorean;3202
+tilde;02DC
+tildebelowcmb;0330
+tildecmb;0303
+tildecomb;0303
+tildedoublecmb;0360
+tildeoperator;223C
+tildeoverlaycmb;0334
+tildeverticalcmb;033E
+timescircle;2297
+tipehahebrew;0596
+tipehalefthebrew;0596
+tippigurmukhi;0A70
+titlocyrilliccmb;0483
+tiwnarmenian;057F
+tlinebelow;1E6F
+tmonospace;FF54
+toarmenian;0569
+tohiragana;3068
+tokatakana;30C8
+tokatakanahalfwidth;FF84
+tonebarextrahighmod;02E5
+tonebarextralowmod;02E9
+tonebarhighmod;02E6
+tonebarlowmod;02E8
+tonebarmidmod;02E7
+tonefive;01BD
+tonesix;0185
+tonetwo;01A8
+tonos;0384
+tonsquare;3327
+topatakthai;0E0F
+tortoiseshellbracketleft;3014
+tortoiseshellbracketleftsmall;FE5D
+tortoiseshellbracketleftvertical;FE39
+tortoiseshellbracketright;3015
+tortoiseshellbracketrightsmall;FE5E
+tortoiseshellbracketrightvertical;FE3A
+totaothai;0E15
+tpalatalhook;01AB
+tparen;24AF
+trademark;2122
+trademarksans;F8EA
+trademarkserif;F6DB
+tretroflexhook;0288
+triagdn;25BC
+triaglf;25C4
+triagrt;25BA
+triagup;25B2
+ts;02A6
+tsadi;05E6
+tsadidagesh;FB46
+tsadidageshhebrew;FB46
+tsadihebrew;05E6
+tsecyrillic;0446
+tsere;05B5
+tsere12;05B5
+tsere1e;05B5
+tsere2b;05B5
+tserehebrew;05B5
+tserenarrowhebrew;05B5
+tserequarterhebrew;05B5
+tserewidehebrew;05B5
+tshecyrillic;045B
+tsuperior;F6F3
+ttabengali;099F
+ttadeva;091F
+ttagujarati;0A9F
+ttagurmukhi;0A1F
+tteharabic;0679
+ttehfinalarabic;FB67
+ttehinitialarabic;FB68
+ttehmedialarabic;FB69
+tthabengali;09A0
+tthadeva;0920
+tthagujarati;0AA0
+tthagurmukhi;0A20
+tturned;0287
+tuhiragana;3064
+tukatakana;30C4
+tukatakanahalfwidth;FF82
+tusmallhiragana;3063
+tusmallkatakana;30C3
+tusmallkatakanahalfwidth;FF6F
+twelvecircle;246B
+twelveparen;247F
+twelveperiod;2493
+twelveroman;217B
+twentycircle;2473
+twentyhangzhou;5344
+twentyparen;2487
+twentyperiod;249B
+two;0032
+twoarabic;0662
+twobengali;09E8
+twocircle;2461
+twocircleinversesansserif;278B
+twodeva;0968
+twodotenleader;2025
+twodotleader;2025
+twodotleadervertical;FE30
+twogujarati;0AE8
+twogurmukhi;0A68
+twohackarabic;0662
+twohangzhou;3022
+twoideographicparen;3221
+twoinferior;2082
+twomonospace;FF12
+twonumeratorbengali;09F5
+twooldstyle;F732
+twoparen;2475
+twoperiod;2489
+twopersian;06F2
+tworoman;2171
+twostroke;01BB
+twosuperior;00B2
+twothai;0E52
+twothirds;2154
+u;0075
+uacute;00FA
+ubar;0289
+ubengali;0989
+ubopomofo;3128
+ubreve;016D
+ucaron;01D4
+ucircle;24E4
+ucircumflex;00FB
+ucircumflexbelow;1E77
+ucyrillic;0443
+udattadeva;0951
+udblacute;0171
+udblgrave;0215
+udeva;0909
+udieresis;00FC
+udieresisacute;01D8
+udieresisbelow;1E73
+udieresiscaron;01DA
+udieresiscyrillic;04F1
+udieresisgrave;01DC
+udieresismacron;01D6
+udotbelow;1EE5
+ugrave;00F9
+ugujarati;0A89
+ugurmukhi;0A09
+uhiragana;3046
+uhookabove;1EE7
+uhorn;01B0
+uhornacute;1EE9
+uhorndotbelow;1EF1
+uhorngrave;1EEB
+uhornhookabove;1EED
+uhorntilde;1EEF
+uhungarumlaut;0171
+uhungarumlautcyrillic;04F3
+uinvertedbreve;0217
+ukatakana;30A6
+ukatakanahalfwidth;FF73
+ukcyrillic;0479
+ukorean;315C
+umacron;016B
+umacroncyrillic;04EF
+umacrondieresis;1E7B
+umatragurmukhi;0A41
+umonospace;FF55
+underscore;005F
+underscoredbl;2017
+underscoremonospace;FF3F
+underscorevertical;FE33
+underscorewavy;FE4F
+union;222A
+universal;2200
+uogonek;0173
+uparen;24B0
+upblock;2580
+upperdothebrew;05C4
+upsilon;03C5
+upsilondieresis;03CB
+upsilondieresistonos;03B0
+upsilonlatin;028A
+upsilontonos;03CD
+uptackbelowcmb;031D
+uptackmod;02D4
+uragurmukhi;0A73
+uring;016F
+ushortcyrillic;045E
+usmallhiragana;3045
+usmallkatakana;30A5
+usmallkatakanahalfwidth;FF69
+ustraightcyrillic;04AF
+ustraightstrokecyrillic;04B1
+utilde;0169
+utildeacute;1E79
+utildebelow;1E75
+uubengali;098A
+uudeva;090A
+uugujarati;0A8A
+uugurmukhi;0A0A
+uumatragurmukhi;0A42
+uuvowelsignbengali;09C2
+uuvowelsigndeva;0942
+uuvowelsigngujarati;0AC2
+uvowelsignbengali;09C1
+uvowelsigndeva;0941
+uvowelsigngujarati;0AC1
+v;0076
+vadeva;0935
+vagujarati;0AB5
+vagurmukhi;0A35
+vakatakana;30F7
+vav;05D5
+vavdagesh;FB35
+vavdagesh65;FB35
+vavdageshhebrew;FB35
+vavhebrew;05D5
+vavholam;FB4B
+vavholamhebrew;FB4B
+vavvavhebrew;05F0
+vavyodhebrew;05F1
+vcircle;24E5
+vdotbelow;1E7F
+vecyrillic;0432
+veharabic;06A4
+vehfinalarabic;FB6B
+vehinitialarabic;FB6C
+vehmedialarabic;FB6D
+vekatakana;30F9
+venus;2640
+verticalbar;007C
+verticallineabovecmb;030D
+verticallinebelowcmb;0329
+verticallinelowmod;02CC
+verticallinemod;02C8
+vewarmenian;057E
+vhook;028B
+vikatakana;30F8
+viramabengali;09CD
+viramadeva;094D
+viramagujarati;0ACD
+visargabengali;0983
+visargadeva;0903
+visargagujarati;0A83
+vmonospace;FF56
+voarmenian;0578
+voicediterationhiragana;309E
+voicediterationkatakana;30FE
+voicedmarkkana;309B
+voicedmarkkanahalfwidth;FF9E
+vokatakana;30FA
+vparen;24B1
+vtilde;1E7D
+vturned;028C
+vuhiragana;3094
+vukatakana;30F4
+w;0077
+wacute;1E83
+waekorean;3159
+wahiragana;308F
+wakatakana;30EF
+wakatakanahalfwidth;FF9C
+wakorean;3158
+wasmallhiragana;308E
+wasmallkatakana;30EE
+wattosquare;3357
+wavedash;301C
+wavyunderscorevertical;FE34
+wawarabic;0648
+wawfinalarabic;FEEE
+wawhamzaabovearabic;0624
+wawhamzaabovefinalarabic;FE86
+wbsquare;33DD
+wcircle;24E6
+wcircumflex;0175
+wdieresis;1E85
+wdotaccent;1E87
+wdotbelow;1E89
+wehiragana;3091
+weierstrass;2118
+wekatakana;30F1
+wekorean;315E
+weokorean;315D
+wgrave;1E81
+whitebullet;25E6
+whitecircle;25CB
+whitecircleinverse;25D9
+whitecornerbracketleft;300E
+whitecornerbracketleftvertical;FE43
+whitecornerbracketright;300F
+whitecornerbracketrightvertical;FE44
+whitediamond;25C7
+whitediamondcontainingblacksmalldiamond;25C8
+whitedownpointingsmalltriangle;25BF
+whitedownpointingtriangle;25BD
+whiteleftpointingsmalltriangle;25C3
+whiteleftpointingtriangle;25C1
+whitelenticularbracketleft;3016
+whitelenticularbracketright;3017
+whiterightpointingsmalltriangle;25B9
+whiterightpointingtriangle;25B7
+whitesmallsquare;25AB
+whitesmilingface;263A
+whitesquare;25A1
+whitestar;2606
+whitetelephone;260F
+whitetortoiseshellbracketleft;3018
+whitetortoiseshellbracketright;3019
+whiteuppointingsmalltriangle;25B5
+whiteuppointingtriangle;25B3
+wihiragana;3090
+wikatakana;30F0
+wikorean;315F
+wmonospace;FF57
+wohiragana;3092
+wokatakana;30F2
+wokatakanahalfwidth;FF66
+won;20A9
+wonmonospace;FFE6
+wowaenthai;0E27
+wparen;24B2
+wring;1E98
+wsuperior;02B7
+wturned;028D
+wynn;01BF
+x;0078
+xabovecmb;033D
+xbopomofo;3112
+xcircle;24E7
+xdieresis;1E8D
+xdotaccent;1E8B
+xeharmenian;056D
+xi;03BE
+xmonospace;FF58
+xparen;24B3
+xsuperior;02E3
+y;0079
+yaadosquare;334E
+yabengali;09AF
+yacute;00FD
+yadeva;092F
+yaekorean;3152
+yagujarati;0AAF
+yagurmukhi;0A2F
+yahiragana;3084
+yakatakana;30E4
+yakatakanahalfwidth;FF94
+yakorean;3151
+yamakkanthai;0E4E
+yasmallhiragana;3083
+yasmallkatakana;30E3
+yasmallkatakanahalfwidth;FF6C
+yatcyrillic;0463
+ycircle;24E8
+ycircumflex;0177
+ydieresis;00FF
+ydotaccent;1E8F
+ydotbelow;1EF5
+yeharabic;064A
+yehbarreearabic;06D2
+yehbarreefinalarabic;FBAF
+yehfinalarabic;FEF2
+yehhamzaabovearabic;0626
+yehhamzaabovefinalarabic;FE8A
+yehhamzaaboveinitialarabic;FE8B
+yehhamzaabovemedialarabic;FE8C
+yehinitialarabic;FEF3
+yehmedialarabic;FEF4
+yehmeeminitialarabic;FCDD
+yehmeemisolatedarabic;FC58
+yehnoonfinalarabic;FC94
+yehthreedotsbelowarabic;06D1
+yekorean;3156
+yen;00A5
+yenmonospace;FFE5
+yeokorean;3155
+yeorinhieuhkorean;3186
+yerahbenyomohebrew;05AA
+yerahbenyomolefthebrew;05AA
+yericyrillic;044B
+yerudieresiscyrillic;04F9
+yesieungkorean;3181
+yesieungpansioskorean;3183
+yesieungsioskorean;3182
+yetivhebrew;059A
+ygrave;1EF3
+yhook;01B4
+yhookabove;1EF7
+yiarmenian;0575
+yicyrillic;0457
+yikorean;3162
+yinyang;262F
+yiwnarmenian;0582
+ymonospace;FF59
+yod;05D9
+yoddagesh;FB39
+yoddageshhebrew;FB39
+yodhebrew;05D9
+yodyodhebrew;05F2
+yodyodpatahhebrew;FB1F
+yohiragana;3088
+yoikorean;3189
+yokatakana;30E8
+yokatakanahalfwidth;FF96
+yokorean;315B
+yosmallhiragana;3087
+yosmallkatakana;30E7
+yosmallkatakanahalfwidth;FF6E
+yotgreek;03F3
+yoyaekorean;3188
+yoyakorean;3187
+yoyakthai;0E22
+yoyingthai;0E0D
+yparen;24B4
+ypogegrammeni;037A
+ypogegrammenigreekcmb;0345
+yr;01A6
+yring;1E99
+ysuperior;02B8
+ytilde;1EF9
+yturned;028E
+yuhiragana;3086
+yuikorean;318C
+yukatakana;30E6
+yukatakanahalfwidth;FF95
+yukorean;3160
+yusbigcyrillic;046B
+yusbigiotifiedcyrillic;046D
+yuslittlecyrillic;0467
+yuslittleiotifiedcyrillic;0469
+yusmallhiragana;3085
+yusmallkatakana;30E5
+yusmallkatakanahalfwidth;FF6D
+yuyekorean;318B
+yuyeokorean;318A
+yyabengali;09DF
+yyadeva;095F
+z;007A
+zaarmenian;0566
+zacute;017A
+zadeva;095B
+zagurmukhi;0A5B
+zaharabic;0638
+zahfinalarabic;FEC6
+zahinitialarabic;FEC7
+zahiragana;3056
+zahmedialarabic;FEC8
+zainarabic;0632
+zainfinalarabic;FEB0
+zakatakana;30B6
+zaqefgadolhebrew;0595
+zaqefqatanhebrew;0594
+zarqahebrew;0598
+zayin;05D6
+zayindagesh;FB36
+zayindageshhebrew;FB36
+zayinhebrew;05D6
+zbopomofo;3117
+zcaron;017E
+zcircle;24E9
+zcircumflex;1E91
+zcurl;0291
+zdot;017C
+zdotaccent;017C
+zdotbelow;1E93
+zecyrillic;0437
+zedescendercyrillic;0499
+zedieresiscyrillic;04DF
+zehiragana;305C
+zekatakana;30BC
+zero;0030
+zeroarabic;0660
+zerobengali;09E6
+zerodeva;0966
+zerogujarati;0AE6
+zerogurmukhi;0A66
+zerohackarabic;0660
+zeroinferior;2080
+zeromonospace;FF10
+zerooldstyle;F730
+zeropersian;06F0
+zerosuperior;2070
+zerothai;0E50
+zerowidthjoiner;FEFF
+zerowidthnonjoiner;200C
+zerowidthspace;200B
+zeta;03B6
+zhbopomofo;3113
+zhearmenian;056A
+zhebrevecyrillic;04C2
+zhecyrillic;0436
+zhedescendercyrillic;0497
+zhedieresiscyrillic;04DD
+zihiragana;3058
+zikatakana;30B8
+zinorhebrew;05AE
+zlinebelow;1E95
+zmonospace;FF5A
+zohiragana;305E
+zokatakana;30BE
+zparen;24B5
+zretroflexhook;0290
+zstroke;01B6
+zuhiragana;305A
+zukatakana;30BA
+# END
+"""
+
+
+_aglfnText = """\
+# -----------------------------------------------------------
+# Copyright 2002-2019 Adobe (http://www.adobe.com/).
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the
@@ -26,10 +4376,9 @@ _aglText = """\
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
-# Neither the name of Adobe Systems Incorporated nor the names
-# of its contributors may be used to endorse or promote
-# products derived from this software without specific prior
-# written permission.
+# Neither the name of Adobe nor the names of its contributors
+# may be used to endorse or promote products derived from this
+# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
@@ -48,7 +4397,7 @@ _aglText = """\
# Name: Adobe Glyph List For New Fonts
# Table version: 1.7
# Date: November 6, 2008
-# URL: http://sourceforge.net/adobe/aglfn/
+# URL: https://github.com/adobe-type-tools/agl-aglfn
#
# Description:
#
@@ -705,13 +5054,14 @@ _aglText = """\
017C;zdotaccent;LATIN SMALL LETTER Z WITH DOT ABOVE
0030;zero;DIGIT ZERO
03B6;zeta;GREEK SMALL LETTER ZETA
-#END
+# END
"""
class AGLError(Exception):
pass
+LEGACY_AGL2UV = {}
AGL2UV = {}
UV2AGL = {}
@@ -720,7 +5070,7 @@ def _builddicts():
lines = _aglText.splitlines()
- parseAGL_RE = re.compile("([0-9A-F]{4});([A-Za-z_0-9.]+);.*?$")
+ parseAGL_RE = re.compile("([A-Za-z0-9]+);((?:[0-9A-F]{4})(?: (?:[0-9A-F]{4}))*)$")
for line in lines:
if not line or line[:1] == '#':
@@ -728,24 +5078,36 @@ def _builddicts():
m = parseAGL_RE.match(line)
if not m:
raise AGLError("syntax error in glyphlist.txt: %s" % repr(line[:20]))
+ unicodes = m.group(2)
+ assert len(unicodes) % 5 == 4
+ unicodes = [int(unicode, 16) for unicode in unicodes.split()]
+ glyphName = tostr(m.group(1))
+ LEGACY_AGL2UV[glyphName] = unicodes
+
+ lines = _aglfnText.splitlines()
+
+ parseAGLFN_RE = re.compile("([0-9A-F]{4});([A-Za-z0-9]+);.*?$")
+
+ for line in lines:
+ if not line or line[:1] == '#':
+ continue
+ m = parseAGLFN_RE.match(line)
+ if not m:
+ raise AGLError("syntax error in aglfn.txt: %s" % repr(line[:20]))
unicode = m.group(1)
assert len(unicode) == 4
unicode = int(unicode, 16)
glyphName = tostr(m.group(2))
- if glyphName in AGL2UV:
- # the above table contains identical duplicates
- assert AGL2UV[glyphName] == unicode
- else:
- AGL2UV[glyphName] = unicode
+ AGL2UV[glyphName] = unicode
UV2AGL[unicode] = glyphName
_builddicts()
def toUnicode(glyph, isZapfDingbats=False):
- """Convert glyph names to Unicode, such as 'longs_t.oldstyle' --> u'ſt'
+ """Convert glyph names to Unicode, such as ``'longs_t.oldstyle'`` --> ``u'ſt'``
- If isZapfDingbats is True, the implementation recognizes additional
+ If ``isZapfDingbats`` is ``True``, the implementation recognizes additional
glyph names (as required by the AGL specification).
"""
# https://github.com/adobe-type-tools/agl-specification#2-the-mapping
@@ -776,14 +5138,9 @@ def _glyphComponentToUnicode(component, isZapfDingbats):
# Otherwise, if the component is in AGL, then map it
# to the corresponding character in that list.
- #
- # TODO: We currently use the AGLFN (Adobe glyph list for new fonts),
- # although the spec actually mandates the legacy AGL which is
- # a superset of the AGLFN.
- # https://github.com/fonttools/fonttools/issues/775
- uchar = AGL2UV.get(component)
- if uchar:
- return unichr(uchar)
+ uchars = LEGACY_AGL2UV.get(component)
+ if uchars:
+ return "".join(map(chr, uchars))
# Otherwise, if the component is of the form "uni" (U+0075,
# U+006E, and U+0069) followed by a sequence of uppercase
@@ -853,7 +5210,7 @@ def _uniToUnicode(component):
if any(c >= 0xD800 and c <= 0xDFFF for c in chars):
# The AGL specification explicitly excluded surrogate pairs.
return None
- return ''.join([unichr(c) for c in chars])
+ return ''.join([chr(c) for c in chars])
_re_u = re.compile("^u([0-9A-F]{4,6})$")
@@ -871,5 +5228,5 @@ def _uToUnicode(component):
return None
if ((value >= 0x0000 and value <= 0xD7FF) or
(value >= 0xE000 and value <= 0x10FFFF)):
- return unichr(value)
+ return chr(value)
return None
diff --git a/Lib/fontTools/cffLib/__init__.py b/Lib/fontTools/cffLib/__init__.py
index e9065407..d4cd7a17 100644
--- a/Lib/fontTools/cffLib/__init__.py
+++ b/Lib/fontTools/cffLib/__init__.py
@@ -1,7 +1,17 @@
-"""cffLib.py -- read/write tools for Adobe CFF fonts."""
+"""cffLib: read/write Adobe CFF fonts
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+OpenType fonts with PostScript outlines contain a completely independent
+font file, Adobe's *Compact Font Format*. So dealing with OpenType fonts
+requires also dealing with CFF. This module allows you to read and write
+fonts written in the CFF format.
+
+In 2016, OpenType 1.8 introduced the `CFF2 <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2>`_
+format which, along with other changes, extended the CFF format to deal with
+the demands of variable fonts. This module parses both original CFF and CFF2.
+
+"""
+
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, tobytes, tostr
from fontTools.misc import sstruct
from fontTools.misc import psCharStrings
from fontTools.misc.arrayTools import unionRect, intRect
@@ -10,6 +20,7 @@ from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.otBase import OTTableWriter
from fontTools.ttLib.tables.otBase import OTTableReader
from fontTools.ttLib.tables import otTables as ot
+from io import BytesIO
import struct
import logging
import re
@@ -29,8 +40,37 @@ maxStackLimit = 513
class CFFFontSet(object):
+ """A CFF font "file" can contain more than one font, although this is
+ extremely rare (and not allowed within OpenType fonts).
+
+ This class is the entry point for parsing a CFF table. To actually
+ manipulate the data inside the CFF font, you will want to access the
+ ``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet``
+ object can either be treated as a dictionary (with appropriate
+ ``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict`
+ objects, or as a list.
+
+ .. code:: python
+
+ from fontTools import ttLib
+ tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf")
+ tt["CFF "].cff
+ # <fontTools.cffLib.CFFFontSet object at 0x101e24c90>
+ tt["CFF "].cff[0] # Here's your actual font data
+ # <fontTools.cffLib.TopDict object at 0x1020f1fd0>
+
+ """
def decompile(self, file, otFont, isCFF2=None):
+ """Parse a binary CFF file into an internal representation. ``file``
+ should be a file handle object. ``otFont`` is the top-level
+ :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
+
+ If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
+ library makes an assertion that the CFF header is of the appropriate
+ version.
+ """
+
self.otFont = otFont
sstruct.unpack(cffHeaderFormat, file.read(3), self)
if isCFF2 is not None:
@@ -79,7 +119,7 @@ class CFFFontSet(object):
"""
if hasattr(nameOrIndex, "__index__"):
index = nameOrIndex.__index__()
- elif isinstance(nameOrIndex, basestring):
+ elif isinstance(nameOrIndex, str):
name = nameOrIndex
try:
index = self.fontNames.index(name)
@@ -90,6 +130,14 @@ class CFFFontSet(object):
return self.topDictIndex[index]
def compile(self, file, otFont, isCFF2=None):
+ """Write the object back into binary representation onto the given file.
+ ``file`` should be a file handle object. ``otFont`` is the top-level
+ :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
+
+ If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
+ library makes an assertion that the CFF header is of the appropriate
+ version.
+ """
self.otFont = otFont
if isCFF2 is not None:
# called from ttLib: assert 'major' value matches expected version
@@ -145,6 +193,16 @@ class CFFFontSet(object):
writer.toFile(file)
def toXML(self, xmlWriter):
+ """Write the object into XML representation onto the given
+ :class:`fontTools.misc.xmlWriter.XMLWriter`.
+
+ .. code:: python
+
+ writer = xmlWriter.XMLWriter(sys.stdout)
+ tt["CFF "].cff.toXML(writer)
+
+ """
+
xmlWriter.simpletag("major", value=self.major)
xmlWriter.newline()
xmlWriter.simpletag("minor", value=self.minor)
@@ -164,6 +222,7 @@ class CFFFontSet(object):
xmlWriter.newline()
def fromXML(self, name, attrs, content, otFont=None):
+ """Reads data from the XML element into the ``CFFFontSet`` object."""
self.otFont = otFont
# set defaults. These will be replaced if there are entries for them
@@ -203,7 +262,7 @@ class CFFFontSet(object):
self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None)
self.topDictIndex.append(topDict)
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
topDict.fromXML(name, attrs, content)
@@ -219,7 +278,7 @@ class CFFFontSet(object):
if not hasattr(self, "GlobalSubrs"):
self.GlobalSubrs = GlobalSubrsIndex()
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
subr = subrCharStringClass()
@@ -231,7 +290,11 @@ class CFFFontSet(object):
self.minor = int(attrs['value'])
def convertCFFToCFF2(self, otFont):
- # This assumes a decompiled CFF table.
+ """Converts this object from CFF format to CFF2 format. This conversion
+ is done 'in-place'. The conversion cannot be reversed.
+
+ This assumes a decompiled CFF table. (i.e. that the object has been
+ filled via :meth:`decompile`.)"""
self.major = 2
cff2GetGlyphOrder = self.otFont.getGlyphOrder
topDictData = TopDictIndex(None, cff2GetGlyphOrder, None)
@@ -308,7 +371,8 @@ class CFFFontSet(object):
class CFFWriter(object):
-
+ """Helper class for serializing CFF data to binary. Used by
+ :meth:`CFFFontSet.compile`."""
def __init__(self, isCFF2):
self.data = []
self.isCFF2 = isCFF2
@@ -368,6 +432,8 @@ def calcOffSize(largestOffset):
class IndexCompiler(object):
+ """Base class for writing CFF `INDEX data <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>`_
+ to binary."""
def __init__(self, items, strings, parent, isCFF2=None):
if isCFF2 is None and hasattr(parent, "isCFF2"):
@@ -447,6 +513,7 @@ class IndexedStringsCompiler(IndexCompiler):
class TopDictIndexCompiler(IndexCompiler):
+ """Helper class for writing the TopDict to binary."""
def getItems(self, items, strings):
out = []
@@ -482,6 +549,9 @@ class TopDictIndexCompiler(IndexCompiler):
class FDArrayIndexCompiler(IndexCompiler):
+ """Helper class for writing the
+ `Font DICT INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#10-font-dict-index-font-dicts-and-fdselect>`_
+ to binary."""
def getItems(self, items, strings):
out = []
@@ -520,6 +590,8 @@ class FDArrayIndexCompiler(IndexCompiler):
class GlobalSubrsCompiler(IndexCompiler):
+ """Helper class for writing the `global subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
+ to binary."""
def getItems(self, items, strings):
out = []
@@ -530,14 +602,17 @@ class GlobalSubrsCompiler(IndexCompiler):
class SubrsCompiler(GlobalSubrsCompiler):
-
+ """Helper class for writing the `local subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
+ to binary."""
+
def setPos(self, pos, endPos):
offset = pos - self.parent.pos
self.parent.rawDict["Subrs"] = offset
class CharStringsCompiler(GlobalSubrsCompiler):
-
+ """Helper class for writing the `CharStrings INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
+ to binary."""
def getItems(self, items, strings):
out = []
for cs in items:
@@ -550,8 +625,9 @@ class CharStringsCompiler(GlobalSubrsCompiler):
class Index(object):
-
- """This class represents what the CFF spec calls an INDEX."""
+ """This class represents what the CFF spec calls an INDEX (an array of
+ variable-sized objects). `Index` items can be addressed and set using
+ Python list indexing."""
compilerClass = IndexCompiler
@@ -609,16 +685,50 @@ class Index(object):
return data
def append(self, item):
+ """Add an item to an INDEX."""
self.items.append(item)
def getCompiler(self, strings, parent, isCFF2=None):
return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
def clear(self):
+ """Empty the INDEX."""
del self.items[:]
class GlobalSubrsIndex(Index):
+ """This index contains all the global subroutines in the font. A global
+ subroutine is a set of ``CharString`` data which is accessible to any
+ glyph in the font, and are used to store repeated instructions - for
+ example, components may be encoded as global subroutines, but so could
+ hinting instructions.
+
+ Remember that when interpreting a ``callgsubr`` instruction (or indeed
+ a ``callsubr`` instruction) that you will need to add the "subroutine
+ number bias" to number given:
+
+ .. code:: python
+
+ tt = ttLib.TTFont("Almendra-Bold.otf")
+ u = tt["CFF "].cff[0].CharStrings["udieresis"]
+ u.decompile()
+
+ u.toXML(XMLWriter(sys.stdout))
+ # <some stuff>
+ # -64 callgsubr <-- Subroutine which implements the dieresis mark
+ # <other stuff>
+
+ tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG
+ # <T2CharString (bytecode) at 103451d10>
+
+ tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT
+ # <T2CharString (source) at 103451390>
+
+ ("The bias applied depends on the number of subrs (gsubrs). If the number of
+ subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less
+ than 33900, it is 1131; otherwise it is 32768.",
+ `Subroutine Operators <https://docs.microsoft.com/en-us/typography/opentype/otspec180/cff2charstr#section4.4>`)
+ """
compilerClass = GlobalSubrsCompiler
subrClass = psCharStrings.T2CharString
@@ -648,6 +758,15 @@ class GlobalSubrsIndex(Index):
return self.subrClass(data, private=private, globalSubrs=self.globalSubrs)
def toXML(self, xmlWriter):
+ """Write the subroutines index into XML representation onto the given
+ :class:`fontTools.misc.xmlWriter.XMLWriter`.
+
+ .. code:: python
+
+ writer = xmlWriter.XMLWriter(sys.stdout)
+ tt["CFF "].cff[0].GlobalSubrs.toXML(writer)
+
+ """
xmlWriter.comment(
"The 'index' attribute is only for humans; "
"it is ignored when parsed.")
@@ -678,10 +797,26 @@ class GlobalSubrsIndex(Index):
class SubrsIndex(GlobalSubrsIndex):
+ """This index contains a glyph's local subroutines. A local subroutine is a
+ private set of ``CharString`` data which is accessible only to the glyph to
+ which the index is attached."""
+
compilerClass = SubrsCompiler
class TopDictIndex(Index):
+ """This index represents the array of ``TopDict`` structures in the font
+ (again, usually only one entry is present). Hence the following calls are
+ equivalent:
+
+ .. code:: python
+
+ tt["CFF "].cff[0]
+ # <fontTools.cffLib.TopDict object at 0x102ed6e50>
+ tt["CFF "].cff.topDictIndex[0]
+ # <fontTools.cffLib.TopDict object at 0x102ed6e50>
+
+ """
compilerClass = TopDictIndexCompiler
@@ -745,7 +880,7 @@ class FDArrayIndex(Index):
return
fontDict = FontDict()
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
fontDict.fromXML(name, attrs, content)
@@ -870,6 +1005,20 @@ class FDSelect(object):
class CharStrings(object):
+ """The ``CharStrings`` in the font represent the instructions for drawing
+ each glyph. This object presents a dictionary interface to the font's
+ CharStrings, indexed by glyph name:
+
+ .. code:: python
+
+ tt["CFF "].cff[0].CharStrings["a"]
+ # <T2CharString (bytecode) at 103451e90>
+
+ See :class:`fontTools.misc.psCharStrings.T1CharString` and
+ :class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile,
+ compile and interpret the glyph drawing instructions in the returned objects.
+
+ """
def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray,
isCFF2=None):
@@ -958,7 +1107,7 @@ class CharStrings(object):
def fromXML(self, name, attrs, content):
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
if name != "CharString":
@@ -1097,7 +1246,7 @@ class ASCIIConverter(SimpleConverter):
return tobytes(value, encoding='ascii')
def xmlWrite(self, xmlWriter, name, value):
- xmlWriter.simpletag(name, value=tounicode(value, encoding="ascii"))
+ xmlWriter.simpletag(name, value=tostr(value, encoding="ascii"))
xmlWriter.newline()
def xmlRead(self, name, attrs, content, parent):
@@ -1113,7 +1262,7 @@ class Latin1Converter(SimpleConverter):
return tobytes(value, encoding='latin1')
def xmlWrite(self, xmlWriter, name, value):
- value = tounicode(value, encoding="latin1")
+ value = tostr(value, encoding="latin1")
if name in ['Notice', 'Copyright']:
value = re.sub(r"[\r\n]\s+", " ", value)
xmlWriter.simpletag(name, value=value)
@@ -1134,7 +1283,7 @@ def parseNum(s):
def parseBlendList(s):
valueList = []
for element in s:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
blendList = attrs["value"].split()
@@ -1210,7 +1359,7 @@ class TableConverter(SimpleConverter):
def xmlRead(self, name, attrs, content, parent):
ob = self.getClass()()
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
ob.fromXML(name, attrs, content)
@@ -1320,6 +1469,20 @@ class CharsetConverter(SimpleConverter):
raise NotImplementedError
assert len(charset) == numGlyphs
log.log(DEBUG, " charset end at %s", file.tell())
+ # make sure glyph names are unique
+ allNames = {}
+ newCharset = []
+ for glyphName in charset:
+ if glyphName in allNames:
+ # make up a new glyphName that's unique
+ n = allNames[glyphName]
+ while (glyphName + "#" + str(n)) in allNames:
+ n += 1
+ allNames[glyphName] = n + 1
+ glyphName = glyphName + "#" + str(n)
+ allNames[glyphName] = 1
+ newCharset.append(glyphName)
+ charset = newCharset
else: # offset == 0 -> no charset data.
if isCID or "CharStrings" not in parent.rawDict:
# We get here only when processing fontDicts from the FDArray of
@@ -1488,7 +1651,7 @@ def parseCharset(numGlyphs, file, strings, isCID, fmt):
class EncodingCompiler(object):
def __init__(self, strings, encoding, parent):
- assert not isinstance(encoding, basestring)
+ assert not isinstance(encoding, str)
data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
if len(data0) < len(data1):
@@ -1559,7 +1722,7 @@ class EncodingConverter(SimpleConverter):
return attrs["name"]
encoding = [".notdef"] * 256
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
code = safeEval(attrs["code"])
@@ -1671,7 +1834,7 @@ class FDArrayConverter(TableConverter):
def xmlRead(self, name, attrs, content, parent):
fdArray = FDArrayIndex()
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
fdArray.fromXML(name, attrs, content)
@@ -1943,6 +2106,8 @@ privateDictOperators2 = [
(11, 'StdVW', 'number', None, None),
((12, 12), 'StemSnapH', 'delta', None, None),
((12, 13), 'StemSnapV', 'delta', None, None),
+ ((12, 17), 'LanguageGroup', 'number', 0, None),
+ ((12, 18), 'ExpansionFactor', 'number', 0.06, None),
(19, 'Subrs', 'number', None, SubrsConverter()),
]
@@ -2074,27 +2239,33 @@ class DictCompiler(object):
def arg_delta_blend(self, value):
- """ A delta list with blend lists has to be *all* blend lists.
- The value is a list is arranged as follows.
- [
- [V0, d0..dn]
- [V1, d0..dn]
- ...
- [Vm, d0..dn]
- ]
- V is the absolute coordinate value from the default font, and d0-dn are
- the delta values from the n regions. Each V is an absolute coordinate
- from the default font.
- We want to return a list:
- [
- [v0, v1..vm]
- [d0..dn]
- ...
- [d0..dn]
- numBlends
- blendOp
- ]
- where each v is relative to the previous default font value.
+ """A delta list with blend lists has to be *all* blend lists.
+
+ The value is a list is arranged as follows::
+
+ [
+ [V0, d0..dn]
+ [V1, d0..dn]
+ ...
+ [Vm, d0..dn]
+ ]
+
+ ``V`` is the absolute coordinate value from the default font, and ``d0-dn``
+ are the delta values from the *n* regions. Each ``V`` is an absolute
+ coordinate from the default font.
+
+ We want to return a list::
+
+ [
+ [v0, v1..vm]
+ [d0..dn]
+ ...
+ [d0..dn]
+ numBlends
+ blendOp
+ ]
+
+ where each ``v`` is relative to the previous default font value.
"""
numMasters = len(value[0])
numBlends = len(value)
@@ -2164,7 +2335,7 @@ class TopDictCompiler(DictCompiler):
self.rawDict["charset"] = charsetCode
if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
encoding = self.dictObj.Encoding
- if not isinstance(encoding, basestring):
+ if not isinstance(encoding, str):
children.append(EncodingCompiler(strings, encoding, self))
else:
if hasattr(self.dictObj, "VarStore"):
@@ -2343,6 +2514,30 @@ class BaseDict(object):
class TopDict(BaseDict):
+ """The ``TopDict`` represents the top-level dictionary holding font
+ information. CFF2 tables contain a restricted set of top-level entries
+ as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_,
+ but CFF tables may contain a wider range of information. This information
+ can be accessed through attributes or through the dictionary returned
+ through the ``rawDict`` property:
+
+ .. code:: python
+
+ font = tt["CFF "].cff[0]
+ font.FamilyName
+ # 'Linux Libertine O'
+ font.rawDict["FamilyName"]
+ # 'Linux Libertine O'
+
+ More information is available in the CFF file's private dictionary, accessed
+ via the ``Private`` property:
+
+ .. code:: python
+
+ tt["CFF "].cff[0].Private.BlueValues
+ # [-15, 0, 515, 515, 666, 666]
+
+ """
defaults = buildDefaults(topDictOperators)
converters = buildConverters(topDictOperators)
@@ -2364,6 +2559,7 @@ class TopDict(BaseDict):
self.order = buildOrder(topDictOperators)
def getGlyphOrder(self):
+ """Returns a list of glyph names in the CFF font."""
return self.charset
def postDecompile(self):
diff --git a/Lib/fontTools/cffLib/specializer.py b/Lib/fontTools/cffLib/specializer.py
index 794a2e98..fbfefa92 100644
--- a/Lib/fontTools/cffLib/specializer.py
+++ b/Lib/fontTools/cffLib/specializer.py
@@ -1,14 +1,23 @@
# -*- coding: utf-8 -*-
-"""T2CharString operator specializer and generalizer."""
+"""T2CharString operator specializer and generalizer.
+
+PostScript glyph drawing operations can be expressed in multiple different
+ways. For example, as well as the ``lineto`` operator, there is also a
+``hlineto`` operator which draws a horizontal line, removing the need to
+specify a ``dx`` coordinate, and a ``vlineto`` operator which draws a
+vertical line, removing the need to specify a ``dy`` coordinate. As well
+as decompiling :class:`fontTools.misc.psCharStrings.T2CharString` objects
+into lists of operations, this module allows for conversion between general
+and specific forms of the operation.
+
+"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.cffLib import maxStackLimit
def stringToProgram(string):
- if isinstance(string, basestring):
+ if isinstance(string, str):
string = string.split()
program = []
for token in string:
@@ -60,7 +69,7 @@ def programToCommands(program, getNumRegions=None):
it = iter(program)
for token in it:
- if not isinstance(token, basestring):
+ if not isinstance(token, str):
stack.append(token)
continue
diff --git a/Lib/fontTools/cffLib/width.py b/Lib/fontTools/cffLib/width.py
index a9e55323..00b859bb 100644
--- a/Lib/fontTools/cffLib/width.py
+++ b/Lib/fontTools/cffLib/width.py
@@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
-"""T2CharString glyph width optimizer."""
+"""T2CharString glyph width optimizer.
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.ttLib import TTFont, getTableClass
+CFF glyphs whose width equals the CFF Private dictionary's ``defaultWidthX``
+value do not need to specify their width in their charstring, saving bytes.
+This module determines the optimum ``defaultWidthX`` and ``nominalWidthX``
+values for a font, when provided with a list of glyph widths."""
+
+from fontTools.ttLib import TTFont
from collections import defaultdict
from operator import add
-from functools import partial, reduce
+from functools import reduce
class missingdict(dict):
@@ -147,17 +150,34 @@ def optimizeWidths(widths):
return default, nominal
+def main(args=None):
+ """Calculate optimum defaultWidthX/nominalWidthX values"""
+
+ import argparse
+ parser = argparse.ArgumentParser(
+ "fonttools cffLib.width",
+ description=main.__doc__,
+ )
+ parser.add_argument('inputs', metavar='FILE', type=str, nargs='+',
+ help="Input TTF files")
+ parser.add_argument('-b', '--brute-force', dest="brute", action="store_true",
+ help="Use brute-force approach (VERY slow)")
+
+ args = parser.parse_args(args)
+
+ for fontfile in args.inputs:
+ font = TTFont(fontfile)
+ hmtx = font['hmtx']
+ widths = [m[0] for m in hmtx.metrics.values()]
+ if args.brute:
+ default, nominal = optimizeWidthsBruteforce(widths)
+ else:
+ default, nominal = optimizeWidths(widths)
+ print("glyphs=%d default=%d nominal=%d byteCost=%d" % (len(widths), default, nominal, byteCost(widths, default, nominal)))
if __name__ == '__main__':
import sys
if len(sys.argv) == 1:
import doctest
sys.exit(doctest.testmod().failed)
- for fontfile in sys.argv[1:]:
- font = TTFont(fontfile)
- hmtx = font['hmtx']
- widths = [m[0] for m in hmtx.metrics.values()]
- default, nominal = optimizeWidths(widths)
- print("glyphs=%d default=%d nominal=%d byteCost=%d" % (len(widths), default, nominal, byteCost(widths, default, nominal)))
- #default, nominal = optimizeWidthsBruteforce(widths)
- #print("glyphs=%d default=%d nominal=%d byteCost=%d" % (len(widths), default, nominal, byteCost(widths, default, nominal)))
+ main()
diff --git a/Lib/fontTools/colorLib/__init__.py b/Lib/fontTools/colorLib/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/Lib/fontTools/colorLib/__init__.py
diff --git a/Lib/fontTools/colorLib/builder.py b/Lib/fontTools/colorLib/builder.py
new file mode 100644
index 00000000..821244af
--- /dev/null
+++ b/Lib/fontTools/colorLib/builder.py
@@ -0,0 +1,637 @@
+"""
+colorLib.builder: Build COLR/CPAL tables from scratch
+
+"""
+import collections
+import copy
+import enum
+from functools import partial
+from math import ceil, log
+from typing import (
+ Any,
+ Dict,
+ Generator,
+ Iterable,
+ List,
+ Mapping,
+ Optional,
+ Sequence,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+)
+from fontTools.misc.fixedTools import fixedToFloat
+from fontTools.ttLib.tables import C_O_L_R_
+from fontTools.ttLib.tables import C_P_A_L_
+from fontTools.ttLib.tables import _n_a_m_e
+from fontTools.ttLib.tables import otTables as ot
+from fontTools.ttLib.tables.otTables import (
+ ExtendMode,
+ CompositeMode,
+ VariableValue,
+ VariableFloat,
+ VariableInt,
+)
+from .errors import ColorLibError
+from .geometry import round_start_circle_stable_containment
+from .table_builder import (
+ convertTupleClass,
+ BuildCallback,
+ TableBuilder,
+)
+
+
+# TODO move type aliases to colorLib.types?
+T = TypeVar("T")
+_Kwargs = Mapping[str, Any]
+_PaintInput = Union[int, _Kwargs, ot.Paint, Tuple[str, "_PaintInput"]]
+_PaintInputList = Sequence[_PaintInput]
+_ColorGlyphsDict = Dict[str, Union[_PaintInputList, _PaintInput]]
+_ColorGlyphsV0Dict = Dict[str, Sequence[Tuple[str, int]]]
+
+
+MAX_PAINT_COLR_LAYER_COUNT = 255
+_DEFAULT_ALPHA = VariableFloat(1.0)
+_MAX_REUSE_LEN = 32
+
+
+def _beforeBuildPaintVarRadialGradient(paint, source, srcMapFn=lambda v: v):
+ # normalize input types (which may or may not specify a varIdx)
+ x0 = convertTupleClass(VariableFloat, source["x0"])
+ y0 = convertTupleClass(VariableFloat, source["y0"])
+ r0 = convertTupleClass(VariableFloat, source["r0"])
+ x1 = convertTupleClass(VariableFloat, source["x1"])
+ y1 = convertTupleClass(VariableFloat, source["y1"])
+ r1 = convertTupleClass(VariableFloat, source["r1"])
+
+ # TODO apparently no builder_test confirms this works (?)
+
+ # avoid abrupt change after rounding when c0 is near c1's perimeter
+ c = round_start_circle_stable_containment(
+ (x0.value, y0.value), r0.value, (x1.value, y1.value), r1.value
+ )
+ x0, y0 = x0._replace(value=c.centre[0]), y0._replace(value=c.centre[1])
+ r0 = r0._replace(value=c.radius)
+
+ # update source to ensure paint is built with corrected values
+ source["x0"] = srcMapFn(x0)
+ source["y0"] = srcMapFn(y0)
+ source["r0"] = srcMapFn(r0)
+ source["x1"] = srcMapFn(x1)
+ source["y1"] = srcMapFn(y1)
+ source["r1"] = srcMapFn(r1)
+
+ return paint, source
+
+
+def _beforeBuildPaintRadialGradient(paint, source):
+ return _beforeBuildPaintVarRadialGradient(paint, source, lambda v: v.value)
+
+
+def _defaultColorIndex():
+ colorIndex = ot.ColorIndex()
+ colorIndex.Alpha = _DEFAULT_ALPHA.value
+ return colorIndex
+
+
+def _defaultVarColorIndex():
+ colorIndex = ot.VarColorIndex()
+ colorIndex.Alpha = _DEFAULT_ALPHA
+ return colorIndex
+
+
+def _defaultColorLine():
+ colorLine = ot.ColorLine()
+ colorLine.Extend = ExtendMode.PAD
+ return colorLine
+
+
+def _defaultVarColorLine():
+ colorLine = ot.VarColorLine()
+ colorLine.Extend = ExtendMode.PAD
+ return colorLine
+
+
+def _buildPaintCallbacks():
+ return {
+ (
+ BuildCallback.BEFORE_BUILD,
+ ot.Paint,
+ ot.PaintFormat.PaintRadialGradient,
+ ): _beforeBuildPaintRadialGradient,
+ (
+ BuildCallback.BEFORE_BUILD,
+ ot.Paint,
+ ot.PaintFormat.PaintVarRadialGradient,
+ ): _beforeBuildPaintVarRadialGradient,
+ (BuildCallback.CREATE_DEFAULT, ot.ColorIndex): _defaultColorIndex,
+ (BuildCallback.CREATE_DEFAULT, ot.VarColorIndex): _defaultVarColorIndex,
+ (BuildCallback.CREATE_DEFAULT, ot.ColorLine): _defaultColorLine,
+ (BuildCallback.CREATE_DEFAULT, ot.VarColorLine): _defaultVarColorLine,
+ }
+
+
+def populateCOLRv0(
+ table: ot.COLR,
+ colorGlyphsV0: _ColorGlyphsV0Dict,
+ glyphMap: Optional[Mapping[str, int]] = None,
+):
+ """Build v0 color layers and add to existing COLR table.
+
+ Args:
+ table: a raw otTables.COLR() object (not ttLib's table_C_O_L_R_).
+ colorGlyphsV0: map of base glyph names to lists of (layer glyph names,
+ color palette index) tuples.
+ glyphMap: a map from glyph names to glyph indices, as returned from
+ TTFont.getReverseGlyphMap(), to optionally sort base records by GID.
+ """
+ if glyphMap is not None:
+ colorGlyphItems = sorted(
+ colorGlyphsV0.items(), key=lambda item: glyphMap[item[0]]
+ )
+ else:
+ colorGlyphItems = colorGlyphsV0.items()
+ baseGlyphRecords = []
+ layerRecords = []
+ for baseGlyph, layers in colorGlyphItems:
+ baseRec = ot.BaseGlyphRecord()
+ baseRec.BaseGlyph = baseGlyph
+ baseRec.FirstLayerIndex = len(layerRecords)
+ baseRec.NumLayers = len(layers)
+ baseGlyphRecords.append(baseRec)
+
+ for layerGlyph, paletteIndex in layers:
+ layerRec = ot.LayerRecord()
+ layerRec.LayerGlyph = layerGlyph
+ layerRec.PaletteIndex = paletteIndex
+ layerRecords.append(layerRec)
+
+ table.BaseGlyphRecordCount = len(baseGlyphRecords)
+ table.BaseGlyphRecordArray = ot.BaseGlyphRecordArray()
+ table.BaseGlyphRecordArray.BaseGlyphRecord = baseGlyphRecords
+ table.LayerRecordArray = ot.LayerRecordArray()
+ table.LayerRecordArray.LayerRecord = layerRecords
+ table.LayerRecordCount = len(layerRecords)
+
+
+def buildCOLR(
+ colorGlyphs: _ColorGlyphsDict,
+ version: Optional[int] = None,
+ glyphMap: Optional[Mapping[str, int]] = None,
+ varStore: Optional[ot.VarStore] = None,
+) -> C_O_L_R_.table_C_O_L_R_:
+ """Build COLR table from color layers mapping.
+ Args:
+ colorGlyphs: map of base glyph name to, either list of (layer glyph name,
+ color palette index) tuples for COLRv0; or a single Paint (dict) or
+ list of Paint for COLRv1.
+ version: the version of COLR table. If None, the version is determined
+ by the presence of COLRv1 paints or variation data (varStore), which
+ require version 1; otherwise, if all base glyphs use only simple color
+ layers, version 0 is used.
+ glyphMap: a map from glyph names to glyph indices, as returned from
+ TTFont.getReverseGlyphMap(), to optionally sort base records by GID.
+ varStore: Optional ItemVarationStore for deltas associated with v1 layer.
+ Return:
+ A new COLR table.
+ """
+ self = C_O_L_R_.table_C_O_L_R_()
+
+ if varStore is not None and version == 0:
+ raise ValueError("Can't add VarStore to COLRv0")
+
+ if version in (None, 0) and not varStore:
+ # split color glyphs into v0 and v1 and encode separately
+ colorGlyphsV0, colorGlyphsV1 = _split_color_glyphs_by_version(colorGlyphs)
+ if version == 0 and colorGlyphsV1:
+ raise ValueError("Can't encode COLRv1 glyphs in COLRv0")
+ else:
+ # unless explicitly requested for v1 or have variations, in which case
+ # we encode all color glyph as v1
+ colorGlyphsV0, colorGlyphsV1 = None, colorGlyphs
+
+ colr = ot.COLR()
+
+ if colorGlyphsV0:
+ populateCOLRv0(colr, colorGlyphsV0, glyphMap)
+ else:
+ colr.BaseGlyphRecordCount = colr.LayerRecordCount = 0
+ colr.BaseGlyphRecordArray = colr.LayerRecordArray = None
+
+ if colorGlyphsV1:
+ colr.LayerV1List, colr.BaseGlyphV1List = buildColrV1(colorGlyphsV1, glyphMap)
+
+ if version is None:
+ version = 1 if (varStore or colorGlyphsV1) else 0
+ elif version not in (0, 1):
+ raise NotImplementedError(version)
+ self.version = colr.Version = version
+
+ if version == 0:
+ self.ColorLayers = self._decompileColorLayersV0(colr)
+ else:
+ colr.VarStore = varStore
+ self.table = colr
+
+ return self
+
+
+class ColorPaletteType(enum.IntFlag):
+ USABLE_WITH_LIGHT_BACKGROUND = 0x0001
+ USABLE_WITH_DARK_BACKGROUND = 0x0002
+
+ @classmethod
+ def _missing_(cls, value):
+ # enforce reserved bits
+ if isinstance(value, int) and (value < 0 or value & 0xFFFC != 0):
+ raise ValueError(f"{value} is not a valid {cls.__name__}")
+ return super()._missing_(value)
+
+
+# None, 'abc' or {'en': 'abc', 'de': 'xyz'}
+_OptionalLocalizedString = Union[None, str, Dict[str, str]]
+
+
+def buildPaletteLabels(
+ labels: Iterable[_OptionalLocalizedString], nameTable: _n_a_m_e.table__n_a_m_e
+) -> List[Optional[int]]:
+ return [
+ nameTable.addMultilingualName(l, mac=False)
+ if isinstance(l, dict)
+ else C_P_A_L_.table_C_P_A_L_.NO_NAME_ID
+ if l is None
+ else nameTable.addMultilingualName({"en": l}, mac=False)
+ for l in labels
+ ]
+
+
+def buildCPAL(
+ palettes: Sequence[Sequence[Tuple[float, float, float, float]]],
+ paletteTypes: Optional[Sequence[ColorPaletteType]] = None,
+ paletteLabels: Optional[Sequence[_OptionalLocalizedString]] = None,
+ paletteEntryLabels: Optional[Sequence[_OptionalLocalizedString]] = None,
+ nameTable: Optional[_n_a_m_e.table__n_a_m_e] = None,
+) -> C_P_A_L_.table_C_P_A_L_:
+ """Build CPAL table from list of color palettes.
+
+ Args:
+ palettes: list of lists of colors encoded as tuples of (R, G, B, A) floats
+ in the range [0..1].
+ paletteTypes: optional list of ColorPaletteType, one for each palette.
+ paletteLabels: optional list of palette labels. Each lable can be either:
+ None (no label), a string (for for default English labels), or a
+ localized string (as a dict keyed with BCP47 language codes).
+ paletteEntryLabels: optional list of palette entry labels, one for each
+ palette entry (see paletteLabels).
+ nameTable: optional name table where to store palette and palette entry
+ labels. Required if either paletteLabels or paletteEntryLabels is set.
+
+ Return:
+ A new CPAL v0 or v1 table, if custom palette types or labels are specified.
+ """
+ if len({len(p) for p in palettes}) != 1:
+ raise ColorLibError("color palettes have different lengths")
+
+ if (paletteLabels or paletteEntryLabels) and not nameTable:
+ raise TypeError(
+ "nameTable is required if palette or palette entries have labels"
+ )
+
+ cpal = C_P_A_L_.table_C_P_A_L_()
+ cpal.numPaletteEntries = len(palettes[0])
+
+ cpal.palettes = []
+ for i, palette in enumerate(palettes):
+ colors = []
+ for j, color in enumerate(palette):
+ if not isinstance(color, tuple) or len(color) != 4:
+ raise ColorLibError(
+ f"In palette[{i}][{j}]: expected (R, G, B, A) tuple, got {color!r}"
+ )
+ if any(v > 1 or v < 0 for v in color):
+ raise ColorLibError(
+ f"palette[{i}][{j}] has invalid out-of-range [0..1] color: {color!r}"
+ )
+ # input colors are RGBA, CPAL encodes them as BGRA
+ red, green, blue, alpha = color
+ colors.append(
+ C_P_A_L_.Color(*(round(v * 255) for v in (blue, green, red, alpha)))
+ )
+ cpal.palettes.append(colors)
+
+ if any(v is not None for v in (paletteTypes, paletteLabels, paletteEntryLabels)):
+ cpal.version = 1
+
+ if paletteTypes is not None:
+ if len(paletteTypes) != len(palettes):
+ raise ColorLibError(
+ f"Expected {len(palettes)} paletteTypes, got {len(paletteTypes)}"
+ )
+ cpal.paletteTypes = [ColorPaletteType(t).value for t in paletteTypes]
+ else:
+ cpal.paletteTypes = [C_P_A_L_.table_C_P_A_L_.DEFAULT_PALETTE_TYPE] * len(
+ palettes
+ )
+
+ if paletteLabels is not None:
+ if len(paletteLabels) != len(palettes):
+ raise ColorLibError(
+ f"Expected {len(palettes)} paletteLabels, got {len(paletteLabels)}"
+ )
+ cpal.paletteLabels = buildPaletteLabels(paletteLabels, nameTable)
+ else:
+ cpal.paletteLabels = [C_P_A_L_.table_C_P_A_L_.NO_NAME_ID] * len(palettes)
+
+ if paletteEntryLabels is not None:
+ if len(paletteEntryLabels) != cpal.numPaletteEntries:
+ raise ColorLibError(
+ f"Expected {cpal.numPaletteEntries} paletteEntryLabels, "
+ f"got {len(paletteEntryLabels)}"
+ )
+ cpal.paletteEntryLabels = buildPaletteLabels(paletteEntryLabels, nameTable)
+ else:
+ cpal.paletteEntryLabels = [
+ C_P_A_L_.table_C_P_A_L_.NO_NAME_ID
+ ] * cpal.numPaletteEntries
+ else:
+ cpal.version = 0
+
+ return cpal
+
+
+# COLR v1 tables
+# See draft proposal at: https://github.com/googlefonts/colr-gradients-spec
+
+
+def _is_colrv0_layer(layer: Any) -> bool:
+ # Consider as COLRv0 layer any sequence of length 2 (be it tuple or list) in which
+ # the first element is a str (the layerGlyph) and the second element is an int
+ # (CPAL paletteIndex).
+ # https://github.com/googlefonts/ufo2ft/issues/426
+ try:
+ layerGlyph, paletteIndex = layer
+ except (TypeError, ValueError):
+ return False
+ else:
+ return isinstance(layerGlyph, str) and isinstance(paletteIndex, int)
+
+
+def _split_color_glyphs_by_version(
+ colorGlyphs: _ColorGlyphsDict,
+) -> Tuple[_ColorGlyphsV0Dict, _ColorGlyphsDict]:
+ colorGlyphsV0 = {}
+ colorGlyphsV1 = {}
+ for baseGlyph, layers in colorGlyphs.items():
+ if all(_is_colrv0_layer(l) for l in layers):
+ colorGlyphsV0[baseGlyph] = layers
+ else:
+ colorGlyphsV1[baseGlyph] = layers
+
+ # sanity check
+ assert set(colorGlyphs) == (set(colorGlyphsV0) | set(colorGlyphsV1))
+
+ return colorGlyphsV0, colorGlyphsV1
+
+
+def _reuse_ranges(num_layers: int) -> Generator[Tuple[int, int], None, None]:
+ # TODO feels like something itertools might have already
+ for lbound in range(num_layers):
+ # Reuse of very large #s of layers is relatively unlikely
+ # +2: we want sequences of at least 2
+ # otData handles single-record duplication
+ for ubound in range(
+ lbound + 2, min(num_layers + 1, lbound + 2 + _MAX_REUSE_LEN)
+ ):
+ yield (lbound, ubound)
+
+
+class LayerV1ListBuilder:
+ slices: List[ot.Paint]
+ layers: List[ot.Paint]
+ reusePool: Mapping[Tuple[Any, ...], int]
+ tuples: Mapping[int, Tuple[Any, ...]]
+ keepAlive: List[ot.Paint] # we need id to remain valid
+
+ def __init__(self):
+ self.slices = []
+ self.layers = []
+ self.reusePool = {}
+ self.tuples = {}
+ self.keepAlive = []
+
+ # We need to intercept construction of PaintColrLayers
+ callbacks = _buildPaintCallbacks()
+ callbacks[
+ (
+ BuildCallback.BEFORE_BUILD,
+ ot.Paint,
+ ot.PaintFormat.PaintColrLayers,
+ )
+ ] = self._beforeBuildPaintColrLayers
+ self.tableBuilder = TableBuilder(callbacks)
+
+ def _paint_tuple(self, paint: ot.Paint):
+ # start simple, who even cares about cyclic graphs or interesting field types
+ def _tuple_safe(value):
+ if isinstance(value, enum.Enum):
+ return value
+ elif hasattr(value, "__dict__"):
+ return tuple(
+ (k, _tuple_safe(v)) for k, v in sorted(value.__dict__.items())
+ )
+ elif isinstance(value, collections.abc.MutableSequence):
+ return tuple(_tuple_safe(e) for e in value)
+ return value
+
+ # Cache the tuples for individual Paint instead of the whole sequence
+ # because the seq could be a transient slice
+ result = self.tuples.get(id(paint), None)
+ if result is None:
+ result = _tuple_safe(paint)
+ self.tuples[id(paint)] = result
+ self.keepAlive.append(paint)
+ return result
+
+ def _as_tuple(self, paints: Sequence[ot.Paint]) -> Tuple[Any, ...]:
+ return tuple(self._paint_tuple(p) for p in paints)
+
+ # COLR layers is unusual in that it modifies shared state
+ # so we need a callback into an object
+ def _beforeBuildPaintColrLayers(self, dest, source):
+ paint = ot.Paint()
+ paint.Format = int(ot.PaintFormat.PaintColrLayers)
+ self.slices.append(paint)
+
+ # Sketchy gymnastics: a sequence input will have dropped it's layers
+ # into NumLayers; get it back
+ if isinstance(source.get("NumLayers", None), collections.abc.Sequence):
+ layers = source["NumLayers"]
+ else:
+ layers = source["Layers"]
+
+ # Convert maps seqs or whatever into typed objects
+ layers = [self.buildPaint(l) for l in layers]
+
+ # No reason to have a colr layers with just one entry
+ if len(layers) == 1:
+ return layers[0], {}
+
+ # Look for reuse, with preference to longer sequences
+ # This may make the layer list smaller
+ found_reuse = True
+ while found_reuse:
+ found_reuse = False
+
+ ranges = sorted(
+ _reuse_ranges(len(layers)),
+ key=lambda t: (t[1] - t[0], t[1], t[0]),
+ reverse=True,
+ )
+ for lbound, ubound in ranges:
+ reuse_lbound = self.reusePool.get(
+ self._as_tuple(layers[lbound:ubound]), -1
+ )
+ if reuse_lbound == -1:
+ continue
+ new_slice = ot.Paint()
+ new_slice.Format = int(ot.PaintFormat.PaintColrLayers)
+ new_slice.NumLayers = ubound - lbound
+ new_slice.FirstLayerIndex = reuse_lbound
+ layers = layers[:lbound] + [new_slice] + layers[ubound:]
+ found_reuse = True
+ break
+
+ # The layer list is now final; if it's too big we need to tree it
+ is_tree = len(layers) > MAX_PAINT_COLR_LAYER_COUNT
+ layers = _build_n_ary_tree(layers, n=MAX_PAINT_COLR_LAYER_COUNT)
+
+ # We now have a tree of sequences with Paint leaves.
+ # Convert the sequences into PaintColrLayers.
+ def listToColrLayers(layer):
+ if isinstance(layer, collections.abc.Sequence):
+ return self.buildPaint(
+ {
+ "Format": ot.PaintFormat.PaintColrLayers,
+ "Layers": [listToColrLayers(l) for l in layer],
+ }
+ )
+ return layer
+
+ layers = [listToColrLayers(l) for l in layers]
+
+ paint.NumLayers = len(layers)
+ paint.FirstLayerIndex = len(self.layers)
+ self.layers.extend(layers)
+
+ # Register our parts for reuse provided we aren't a tree
+ # If we are a tree the leaves registered for reuse and that will suffice
+ if not is_tree:
+ for lbound, ubound in _reuse_ranges(len(layers)):
+ self.reusePool[self._as_tuple(layers[lbound:ubound])] = (
+ lbound + paint.FirstLayerIndex
+ )
+
+ # we've fully built dest; empty source prevents generalized build from kicking in
+ return paint, {}
+
+ def buildPaint(self, paint: _PaintInput) -> ot.Paint:
+ return self.tableBuilder.build(ot.Paint, paint)
+
+ def build(self) -> ot.LayerV1List:
+ layers = ot.LayerV1List()
+ layers.LayerCount = len(self.layers)
+ layers.Paint = self.layers
+ return layers
+
+
+def buildBaseGlyphV1Record(
+ baseGlyph: str, layerBuilder: LayerV1ListBuilder, paint: _PaintInput
+) -> ot.BaseGlyphV1List:
+ self = ot.BaseGlyphV1Record()
+ self.BaseGlyph = baseGlyph
+ self.Paint = layerBuilder.buildPaint(paint)
+ return self
+
+
+def _format_glyph_errors(errors: Mapping[str, Exception]) -> str:
+ lines = []
+ for baseGlyph, error in sorted(errors.items()):
+ lines.append(f" {baseGlyph} => {type(error).__name__}: {error}")
+ return "\n".join(lines)
+
+
+def buildColrV1(
+ colorGlyphs: _ColorGlyphsDict,
+ glyphMap: Optional[Mapping[str, int]] = None,
+) -> Tuple[ot.LayerV1List, ot.BaseGlyphV1List]:
+ if glyphMap is not None:
+ colorGlyphItems = sorted(
+ colorGlyphs.items(), key=lambda item: glyphMap[item[0]]
+ )
+ else:
+ colorGlyphItems = colorGlyphs.items()
+
+ errors = {}
+ baseGlyphs = []
+ layerBuilder = LayerV1ListBuilder()
+ for baseGlyph, paint in colorGlyphItems:
+ try:
+ baseGlyphs.append(buildBaseGlyphV1Record(baseGlyph, layerBuilder, paint))
+
+ except (ColorLibError, OverflowError, ValueError, TypeError) as e:
+ errors[baseGlyph] = e
+
+ if errors:
+ failed_glyphs = _format_glyph_errors(errors)
+ exc = ColorLibError(f"Failed to build BaseGlyphV1List:\n{failed_glyphs}")
+ exc.errors = errors
+ raise exc from next(iter(errors.values()))
+
+ layers = layerBuilder.build()
+ glyphs = ot.BaseGlyphV1List()
+ glyphs.BaseGlyphCount = len(baseGlyphs)
+ glyphs.BaseGlyphV1Record = baseGlyphs
+ return (layers, glyphs)
+
+
+def _build_n_ary_tree(leaves, n):
+ """Build N-ary tree from sequence of leaf nodes.
+
+ Return a list of lists where each non-leaf node is a list containing
+ max n nodes.
+ """
+ if not leaves:
+ return []
+
+ assert n > 1
+
+ depth = ceil(log(len(leaves), n))
+
+ if depth <= 1:
+ return list(leaves)
+
+ # Fully populate complete subtrees of root until we have enough leaves left
+ root = []
+ unassigned = None
+ full_step = n ** (depth - 1)
+ for i in range(0, len(leaves), full_step):
+ subtree = leaves[i : i + full_step]
+ if len(subtree) < full_step:
+ unassigned = subtree
+ break
+ while len(subtree) > n:
+ subtree = [subtree[k : k + n] for k in range(0, len(subtree), n)]
+ root.append(subtree)
+
+ if unassigned:
+ # Recurse to fill the last subtree, which is the only partially populated one
+ subtree = _build_n_ary_tree(unassigned, n)
+ if len(subtree) <= n - len(root):
+ # replace last subtree with its children if they can still fit
+ root.extend(subtree)
+ else:
+ root.append(subtree)
+ assert len(root) <= n
+
+ return root
diff --git a/Lib/fontTools/colorLib/errors.py b/Lib/fontTools/colorLib/errors.py
new file mode 100644
index 00000000..a0bdda17
--- /dev/null
+++ b/Lib/fontTools/colorLib/errors.py
@@ -0,0 +1,3 @@
+
+class ColorLibError(Exception):
+ pass
diff --git a/Lib/fontTools/colorLib/geometry.py b/Lib/fontTools/colorLib/geometry.py
new file mode 100644
index 00000000..e62aead1
--- /dev/null
+++ b/Lib/fontTools/colorLib/geometry.py
@@ -0,0 +1,145 @@
+"""Helpers for manipulating 2D points and vectors in COLR table."""
+
+from math import copysign, cos, hypot, pi
+from fontTools.misc.roundTools import otRound
+
+
+def _vector_between(origin, target):
+ return (target[0] - origin[0], target[1] - origin[1])
+
+
+def _round_point(pt):
+ return (otRound(pt[0]), otRound(pt[1]))
+
+
+def _unit_vector(vec):
+ length = hypot(*vec)
+ if length == 0:
+ return None
+ return (vec[0] / length, vec[1] / length)
+
+
+# This is the same tolerance used by Skia's SkTwoPointConicalGradient.cpp to detect
+# when a radial gradient's focal point lies on the end circle.
+_NEARLY_ZERO = 1 / (1 << 12) # 0.000244140625
+
+
+# The unit vector's X and Y components are respectively
+# U = (cos(α), sin(α))
+# where α is the angle between the unit vector and the positive x axis.
+_UNIT_VECTOR_THRESHOLD = cos(3 / 8 * pi) # == sin(1/8 * pi) == 0.38268343236508984
+
+
+def _rounding_offset(direction):
+ # Return 2-tuple of -/+ 1.0 or 0.0 approximately based on the direction vector.
+ # We divide the unit circle in 8 equal slices oriented towards the cardinal
+ # (N, E, S, W) and intermediate (NE, SE, SW, NW) directions. To each slice we
+ # map one of the possible cases: -1, 0, +1 for either X and Y coordinate.
+ # E.g. Return (+1.0, -1.0) if unit vector is oriented towards SE, or
+ # (-1.0, 0.0) if it's pointing West, etc.
+ uv = _unit_vector(direction)
+ if not uv:
+ return (0, 0)
+
+ result = []
+ for uv_component in uv:
+ if -_UNIT_VECTOR_THRESHOLD <= uv_component < _UNIT_VECTOR_THRESHOLD:
+ # unit vector component near 0: direction almost orthogonal to the
+ # direction of the current axis, thus keep coordinate unchanged
+ result.append(0)
+ else:
+ # nudge coord by +/- 1.0 in direction of unit vector
+ result.append(copysign(1.0, uv_component))
+ return tuple(result)
+
+
+class Circle:
+ def __init__(self, centre, radius):
+ self.centre = centre
+ self.radius = radius
+
+ def __repr__(self):
+ return f"Circle(centre={self.centre}, radius={self.radius})"
+
+ def round(self):
+ return Circle(_round_point(self.centre), otRound(self.radius))
+
+ def inside(self, outer_circle):
+ dist = self.radius + hypot(*_vector_between(self.centre, outer_circle.centre))
+ return (
+ abs(outer_circle.radius - dist) <= _NEARLY_ZERO
+ or outer_circle.radius > dist
+ )
+
+ def concentric(self, other):
+ return self.centre == other.centre
+
+ def move(self, dx, dy):
+ self.centre = (self.centre[0] + dx, self.centre[1] + dy)
+
+
+def round_start_circle_stable_containment(c0, r0, c1, r1):
+ """Round start circle so that it stays inside/outside end circle after rounding.
+
+ The rounding of circle coordinates to integers may cause an abrupt change
+ if the start circle c0 is so close to the end circle c1's perimiter that
+ it ends up falling outside (or inside) as a result of the rounding.
+ To keep the gradient unchanged, we nudge it in the right direction.
+
+ See:
+ https://github.com/googlefonts/colr-gradients-spec/issues/204
+ https://github.com/googlefonts/picosvg/issues/158
+ """
+ start, end = Circle(c0, r0), Circle(c1, r1)
+
+ inside_before_round = start.inside(end)
+
+ round_start = start.round()
+ round_end = end.round()
+ inside_after_round = round_start.inside(round_end)
+
+ if inside_before_round == inside_after_round:
+ return round_start
+ elif inside_after_round:
+ # start was outside before rounding: we need to push start away from end
+ direction = _vector_between(round_end.centre, round_start.centre)
+ radius_delta = +1.0
+ else:
+ # start was inside before rounding: we need to push start towards end
+ direction = _vector_between(round_start.centre, round_end.centre)
+ radius_delta = -1.0
+ dx, dy = _rounding_offset(direction)
+
+ # At most 2 iterations ought to be enough to converge. Before the loop, we
+ # know the start circle didn't keep containment after normal rounding; thus
+ # we continue adjusting by -/+ 1.0 until containment is restored.
+ # Normal rounding can at most move each coordinates -/+0.5; in the worst case
+ # both the start and end circle's centres and radii will be rounded in opposite
+ # directions, e.g. when they move along a 45 degree diagonal:
+ # c0 = (1.5, 1.5) ===> (2.0, 2.0)
+ # r0 = 0.5 ===> 1.0
+ # c1 = (0.499, 0.499) ===> (0.0, 0.0)
+ # r1 = 2.499 ===> 2.0
+ # In this example, the relative distance between the circles, calculated
+ # as r1 - (r0 + distance(c0, c1)) is initially 0.57437 (c0 is inside c1), and
+ # -1.82842 after rounding (c0 is now outside c1). Nudging c0 by -1.0 on both
+ # x and y axes moves it towards c1 by hypot(-1.0, -1.0) = 1.41421. Two of these
+ # moves cover twice that distance, which is enough to restore containment.
+ max_attempts = 2
+ for _ in range(max_attempts):
+ if round_start.concentric(round_end):
+ # can't move c0 towards c1 (they are the same), so we change the radius
+ round_start.radius += radius_delta
+ assert round_start.radius >= 0
+ else:
+ round_start.move(dx, dy)
+ if inside_before_round == round_start.inside(round_end):
+ break
+ else: # likely a bug
+ raise AssertionError(
+ f"Rounding circle {start} "
+ f"{'inside' if inside_before_round else 'outside'} "
+ f"{end} failed after {max_attempts} attempts!"
+ )
+
+ return round_start
diff --git a/Lib/fontTools/colorLib/table_builder.py b/Lib/fontTools/colorLib/table_builder.py
new file mode 100644
index 00000000..6fba6b0f
--- /dev/null
+++ b/Lib/fontTools/colorLib/table_builder.py
@@ -0,0 +1,234 @@
+"""
+colorLib.table_builder: Generic helper for filling in BaseTable derivatives from tuples and maps and such.
+
+"""
+
+import collections
+import enum
+from fontTools.ttLib.tables.otBase import (
+ BaseTable,
+ FormatSwitchingBaseTable,
+ UInt8FormatSwitchingBaseTable,
+)
+from fontTools.ttLib.tables.otConverters import (
+ ComputedInt,
+ SimpleValue,
+ Struct,
+ Short,
+ UInt8,
+ UShort,
+ VarInt16,
+ VarUInt16,
+ IntValue,
+ FloatValue,
+)
+from fontTools.misc.roundTools import otRound
+
+
+class BuildCallback(enum.Enum):
+ """Keyed on (BEFORE_BUILD, class[, Format if available]).
+ Receives (dest, source).
+ Should return (dest, source), which can be new objects.
+ """
+
+ BEFORE_BUILD = enum.auto()
+
+ """Keyed on (AFTER_BUILD, class[, Format if available]).
+ Receives (dest).
+ Should return dest, which can be a new object.
+ """
+ AFTER_BUILD = enum.auto()
+
+ """Keyed on (CREATE_DEFAULT, class).
+ Receives no arguments.
+ Should return a new instance of class.
+ """
+ CREATE_DEFAULT = enum.auto()
+
+
+def _assignable(convertersByName):
+ return {k: v for k, v in convertersByName.items() if not isinstance(v, ComputedInt)}
+
+
+def convertTupleClass(tupleClass, value):
+ if isinstance(value, tupleClass):
+ return value
+ if isinstance(value, tuple):
+ return tupleClass(*value)
+ return tupleClass(value)
+
+
+def _isNonStrSequence(value):
+ return isinstance(value, collections.abc.Sequence) and not isinstance(value, str)
+
+
+def _set_format(dest, source):
+ if _isNonStrSequence(source):
+ assert len(source) > 0, f"{type(dest)} needs at least format from {source}"
+ dest.Format = source[0]
+ source = source[1:]
+ elif isinstance(source, collections.abc.Mapping):
+ assert "Format" in source, f"{type(dest)} needs at least Format from {source}"
+ dest.Format = source["Format"]
+ else:
+ raise ValueError(f"Not sure how to populate {type(dest)} from {source}")
+
+ assert isinstance(
+ dest.Format, collections.abc.Hashable
+ ), f"{type(dest)} Format is not hashable: {dest.Format}"
+ assert (
+ dest.Format in dest.convertersByName
+ ), f"{dest.Format} invalid Format of {cls}"
+
+ return source
+
+
+class TableBuilder:
+ """
+ Helps to populate things derived from BaseTable from maps, tuples, etc.
+
+ A table of lifecycle callbacks may be provided to add logic beyond what is possible
+ based on otData info for the target class. See BuildCallbacks.
+ """
+
+ def __init__(self, callbackTable=None):
+ if callbackTable is None:
+ callbackTable = {}
+ self._callbackTable = callbackTable
+
+ def _convert(self, dest, field, converter, value):
+ tupleClass = getattr(converter, "tupleClass", None)
+ enumClass = getattr(converter, "enumClass", None)
+
+ if tupleClass:
+ value = convertTupleClass(tupleClass, value)
+
+ elif enumClass:
+ if isinstance(value, enumClass):
+ pass
+ elif isinstance(value, str):
+ try:
+ value = getattr(enumClass, value.upper())
+ except AttributeError:
+ raise ValueError(f"{value} is not a valid {enumClass}")
+ else:
+ value = enumClass(value)
+
+ elif isinstance(converter, IntValue):
+ value = otRound(value)
+ elif isinstance(converter, FloatValue):
+ value = float(value)
+
+ elif isinstance(converter, Struct):
+ if converter.repeat:
+ if _isNonStrSequence(value):
+ value = [self.build(converter.tableClass, v) for v in value]
+ else:
+ value = [self.build(converter.tableClass, value)]
+ setattr(dest, converter.repeat, len(value))
+ else:
+ value = self.build(converter.tableClass, value)
+ elif callable(converter):
+ value = converter(value)
+
+ setattr(dest, field, value)
+
+ def build(self, cls, source):
+ assert issubclass(cls, BaseTable)
+
+ if isinstance(source, cls):
+ return source
+
+ callbackKey = (cls,)
+ dest = self._callbackTable.get(
+ (BuildCallback.CREATE_DEFAULT,) + callbackKey, lambda: cls()
+ )()
+ assert isinstance(dest, cls)
+
+ convByName = _assignable(cls.convertersByName)
+ skippedFields = set()
+
+ # For format switchers we need to resolve converters based on format
+ if issubclass(cls, FormatSwitchingBaseTable):
+ source = _set_format(dest, source)
+
+ convByName = _assignable(convByName[dest.Format])
+ skippedFields.add("Format")
+ callbackKey = (cls, dest.Format)
+
+ # Convert sequence => mapping so before thunk only has to handle one format
+ if _isNonStrSequence(source):
+ # Sequence (typically list or tuple) assumed to match fields in declaration order
+ assert len(source) <= len(
+ convByName
+ ), f"Sequence of {len(source)} too long for {cls}; expected <= {len(convByName)} values"
+ source = dict(zip(convByName.keys(), source))
+
+ dest, source = self._callbackTable.get(
+ (BuildCallback.BEFORE_BUILD,) + callbackKey, lambda d, s: (d, s)
+ )(dest, source)
+
+ if isinstance(source, collections.abc.Mapping):
+ for field, value in source.items():
+ if field in skippedFields:
+ continue
+ converter = convByName.get(field, None)
+ if not converter:
+ raise ValueError(
+ f"Unrecognized field {field} for {cls}; expected one of {sorted(convByName.keys())}"
+ )
+ self._convert(dest, field, converter, value)
+ else:
+ # let's try as a 1-tuple
+ dest = self.build(cls, (source,))
+
+ dest = self._callbackTable.get(
+ (BuildCallback.AFTER_BUILD,) + callbackKey, lambda d: d
+ )(dest)
+
+ return dest
+
+
+class TableUnbuilder:
+ def __init__(self, callbackTable=None):
+ if callbackTable is None:
+ callbackTable = {}
+ self._callbackTable = callbackTable
+
+ def unbuild(self, table):
+ assert isinstance(table, BaseTable)
+
+ source = {}
+
+ callbackKey = (type(table),)
+ if isinstance(table, FormatSwitchingBaseTable):
+ source["Format"] = int(table.Format)
+ callbackKey += (table.Format,)
+
+ for converter in table.getConverters():
+ if isinstance(converter, ComputedInt):
+ continue
+ value = getattr(table, converter.name)
+
+ tupleClass = getattr(converter, "tupleClass", None)
+ enumClass = getattr(converter, "enumClass", None)
+ if tupleClass:
+ source[converter.name] = tuple(value)
+ elif enumClass:
+ source[converter.name] = value.name.lower()
+ elif isinstance(converter, Struct):
+ if converter.repeat:
+ source[converter.name] = [self.unbuild(v) for v in value]
+ else:
+ source[converter.name] = self.unbuild(value)
+ elif isinstance(converter, SimpleValue):
+ # "simple" values (e.g. int, float, str) need no further un-building
+ source[converter.name] = value
+ else:
+ raise NotImplementedError(
+ "Don't know how unbuild {value!r} with {converter!r}"
+ )
+
+ source = self._callbackTable.get(callbackKey, lambda s: s)(source)
+
+ return source
diff --git a/Lib/fontTools/colorLib/unbuilder.py b/Lib/fontTools/colorLib/unbuilder.py
new file mode 100644
index 00000000..43582bde
--- /dev/null
+++ b/Lib/fontTools/colorLib/unbuilder.py
@@ -0,0 +1,79 @@
+from fontTools.ttLib.tables import otTables as ot
+from .table_builder import TableUnbuilder
+
+
+def unbuildColrV1(layerV1List, baseGlyphV1List):
+ unbuilder = LayerV1ListUnbuilder(layerV1List.Paint)
+ return {
+ rec.BaseGlyph: unbuilder.unbuildPaint(rec.Paint)
+ for rec in baseGlyphV1List.BaseGlyphV1Record
+ }
+
+
+def _flatten(lst):
+ for el in lst:
+ if isinstance(el, list):
+ yield from _flatten(el)
+ else:
+ yield el
+
+
+class LayerV1ListUnbuilder:
+ def __init__(self, layers):
+ self.layers = layers
+
+ callbacks = {
+ (
+ ot.Paint,
+ ot.PaintFormat.PaintColrLayers,
+ ): self._unbuildPaintColrLayers,
+ }
+ self.tableUnbuilder = TableUnbuilder(callbacks)
+
+ def unbuildPaint(self, paint):
+ assert isinstance(paint, ot.Paint)
+ return self.tableUnbuilder.unbuild(paint)
+
+ def _unbuildPaintColrLayers(self, source):
+ assert source["Format"] == ot.PaintFormat.PaintColrLayers
+
+ layers = list(
+ _flatten(
+ [
+ self.unbuildPaint(childPaint)
+ for childPaint in self.layers[
+ source["FirstLayerIndex"] : source["FirstLayerIndex"]
+ + source["NumLayers"]
+ ]
+ ]
+ )
+ )
+
+ if len(layers) == 1:
+ return layers[0]
+
+ return {"Format": source["Format"], "Layers": layers}
+
+
+if __name__ == "__main__":
+ from pprint import pprint
+ import sys
+ from fontTools.ttLib import TTFont
+
+ try:
+ fontfile = sys.argv[1]
+ except IndexError:
+ sys.exit("usage: fonttools colorLib.unbuilder FONTFILE")
+
+ font = TTFont(fontfile)
+ colr = font["COLR"]
+ if colr.version < 1:
+ sys.exit(f"error: No COLR table version=1 found in {fontfile}")
+
+ colorGlyphs = unbuildColrV1(
+ colr.table.LayerV1List,
+ colr.table.BaseGlyphV1List,
+ ignoreVarIdx=not colr.table.VarStore,
+ )
+
+ pprint(colorGlyphs)
diff --git a/Lib/fontTools/cu2qu/__init__.py b/Lib/fontTools/cu2qu/__init__.py
new file mode 100644
index 00000000..4ae6356e
--- /dev/null
+++ b/Lib/fontTools/cu2qu/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .cu2qu import *
diff --git a/Lib/fontTools/cu2qu/__main__.py b/Lib/fontTools/cu2qu/__main__.py
new file mode 100644
index 00000000..084bf8f9
--- /dev/null
+++ b/Lib/fontTools/cu2qu/__main__.py
@@ -0,0 +1,6 @@
+import sys
+from .cli import main
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/Lib/fontTools/cu2qu/cli.py b/Lib/fontTools/cu2qu/cli.py
new file mode 100644
index 00000000..d4e83b88
--- /dev/null
+++ b/Lib/fontTools/cu2qu/cli.py
@@ -0,0 +1,177 @@
+import os
+import argparse
+import logging
+import shutil
+import multiprocessing as mp
+from contextlib import closing
+from functools import partial
+
+import fontTools
+from .ufo import font_to_quadratic, fonts_to_quadratic
+
+ufo_module = None
+try:
+ import ufoLib2 as ufo_module
+except ImportError:
+ try:
+ import defcon as ufo_module
+ except ImportError as e:
+ pass
+
+
+logger = logging.getLogger("fontTools.cu2qu")
+
+
+def _cpu_count():
+ try:
+ return mp.cpu_count()
+ except NotImplementedError: # pragma: no cover
+ return 1
+
+
+def _font_to_quadratic(input_path, output_path=None, **kwargs):
+ ufo = ufo_module.Font(input_path)
+ logger.info('Converting curves for %s', input_path)
+ if font_to_quadratic(ufo, **kwargs):
+ logger.info("Saving %s", output_path)
+ if output_path:
+ ufo.save(output_path)
+ else:
+ ufo.save() # save in-place
+ elif output_path:
+ _copytree(input_path, output_path)
+
+
+def _samepath(path1, path2):
+ # TODO on python3+, there's os.path.samefile
+ path1 = os.path.normcase(os.path.abspath(os.path.realpath(path1)))
+ path2 = os.path.normcase(os.path.abspath(os.path.realpath(path2)))
+ return path1 == path2
+
+
+def _copytree(input_path, output_path):
+ if _samepath(input_path, output_path):
+ logger.debug("input and output paths are the same file; skipped copy")
+ return
+ if os.path.exists(output_path):
+ shutil.rmtree(output_path)
+ shutil.copytree(input_path, output_path)
+
+
+def main(args=None):
+ """Convert a UFO font from cubic to quadratic curves"""
+ parser = argparse.ArgumentParser(prog="cu2qu")
+ parser.add_argument(
+ "--version", action="version", version=fontTools.__version__)
+ parser.add_argument(
+ "infiles",
+ nargs="+",
+ metavar="INPUT",
+ help="one or more input UFO source file(s).")
+ parser.add_argument("-v", "--verbose", action="count", default=0)
+ parser.add_argument(
+ "-e",
+ "--conversion-error",
+ type=float,
+ metavar="ERROR",
+ default=None,
+ help="maxiumum approximation error measured in EM (default: 0.001)")
+ parser.add_argument(
+ "--keep-direction",
+ dest="reverse_direction",
+ action="store_false",
+ help="do not reverse the contour direction")
+
+ mode_parser = parser.add_mutually_exclusive_group()
+ mode_parser.add_argument(
+ "-i",
+ "--interpolatable",
+ action="store_true",
+ help="whether curve conversion should keep interpolation compatibility"
+ )
+ mode_parser.add_argument(
+ "-j",
+ "--jobs",
+ type=int,
+ nargs="?",
+ default=1,
+ const=_cpu_count(),
+ metavar="N",
+ help="Convert using N multiple processes (default: %(default)s)")
+
+ output_parser = parser.add_mutually_exclusive_group()
+ output_parser.add_argument(
+ "-o",
+ "--output-file",
+ default=None,
+ metavar="OUTPUT",
+ help=("output filename for the converted UFO. By default fonts are "
+ "modified in place. This only works with a single input."))
+ output_parser.add_argument(
+ "-d",
+ "--output-dir",
+ default=None,
+ metavar="DIRECTORY",
+ help="output directory where to save converted UFOs")
+
+ options = parser.parse_args(args)
+
+ if ufo_module is None:
+ parser.error("Either ufoLib2 or defcon are required to run this script.")
+
+ if not options.verbose:
+ level = "WARNING"
+ elif options.verbose == 1:
+ level = "INFO"
+ else:
+ level = "DEBUG"
+ logging.basicConfig(level=level)
+
+ if len(options.infiles) > 1 and options.output_file:
+ parser.error("-o/--output-file can't be used with multile inputs")
+
+ if options.output_dir:
+ output_dir = options.output_dir
+ if not os.path.exists(output_dir):
+ os.mkdir(output_dir)
+ elif not os.path.isdir(output_dir):
+ parser.error("'%s' is not a directory" % output_dir)
+ output_paths = [
+ os.path.join(output_dir, os.path.basename(p))
+ for p in options.infiles
+ ]
+ elif options.output_file:
+ output_paths = [options.output_file]
+ else:
+ # save in-place
+ output_paths = [None] * len(options.infiles)
+
+ kwargs = dict(dump_stats=options.verbose > 0,
+ max_err_em=options.conversion_error,
+ reverse_direction=options.reverse_direction)
+
+ if options.interpolatable:
+ logger.info('Converting curves compatibly')
+ ufos = [ufo_module.Font(infile) for infile in options.infiles]
+ if fonts_to_quadratic(ufos, **kwargs):
+ for ufo, output_path in zip(ufos, output_paths):
+ logger.info("Saving %s", output_path)
+ if output_path:
+ ufo.save(output_path)
+ else:
+ ufo.save()
+ else:
+ for input_path, output_path in zip(options.infiles, output_paths):
+ if output_path:
+ _copytree(input_path, output_path)
+ else:
+ jobs = min(len(options.infiles),
+ options.jobs) if options.jobs > 1 else 1
+ if jobs > 1:
+ func = partial(_font_to_quadratic, **kwargs)
+ logger.info('Running %d parallel processes', jobs)
+ with closing(mp.Pool(jobs)) as pool:
+ pool.starmap(func, zip(options.infiles, output_paths))
+ else:
+ for input_path, output_path in zip(options.infiles, output_paths):
+ _font_to_quadratic(input_path, output_path, **kwargs)
diff --git a/Lib/fontTools/cu2qu/cu2qu.py b/Lib/fontTools/cu2qu/cu2qu.py
new file mode 100644
index 00000000..c9ce93ae
--- /dev/null
+++ b/Lib/fontTools/cu2qu/cu2qu.py
@@ -0,0 +1,496 @@
+#cython: language_level=3
+#distutils: define_macros=CYTHON_TRACE_NOGIL=1
+
+# Copyright 2015 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+try:
+ import cython
+except ImportError:
+ # if cython not installed, use mock module with no-op decorators and types
+ from fontTools.misc import cython
+
+import math
+
+from .errors import Error as Cu2QuError, ApproxNotFoundError
+
+
+__all__ = ['curve_to_quadratic', 'curves_to_quadratic']
+
+MAX_N = 100
+
+NAN = float("NaN")
+
+
+if cython.compiled:
+ # Yep, I'm compiled.
+ COMPILED = True
+else:
+ # Just a lowly interpreted script.
+ COMPILED = False
+
+
+@cython.cfunc
+@cython.inline
+@cython.returns(cython.double)
+@cython.locals(v1=cython.complex, v2=cython.complex)
+def dot(v1, v2):
+ """Return the dot product of two vectors.
+
+ Args:
+ v1 (complex): First vector.
+ v2 (complex): Second vector.
+
+ Returns:
+ double: Dot product.
+ """
+ return (v1 * v2.conjugate()).real
+
+
+@cython.cfunc
+@cython.inline
+@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
+@cython.locals(_1=cython.complex, _2=cython.complex, _3=cython.complex, _4=cython.complex)
+def calc_cubic_points(a, b, c, d):
+ _1 = d
+ _2 = (c / 3.0) + d
+ _3 = (b + c) / 3.0 + _2
+ _4 = a + d + c + b
+ return _1, _2, _3, _4
+
+
+@cython.cfunc
+@cython.inline
+@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex)
+@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
+def calc_cubic_parameters(p0, p1, p2, p3):
+ c = (p1 - p0) * 3.0
+ b = (p2 - p1) * 3.0 - c
+ d = p0
+ a = p3 - d - c - b
+ return a, b, c, d
+
+
+@cython.cfunc
+@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex)
+def split_cubic_into_n_iter(p0, p1, p2, p3, n):
+ """Split a cubic Bezier into n equal parts.
+
+ Splits the curve into `n` equal parts by curve time.
+ (t=0..1/n, t=1/n..2/n, ...)
+
+ Args:
+ p0 (complex): Start point of curve.
+ p1 (complex): First handle of curve.
+ p2 (complex): Second handle of curve.
+ p3 (complex): End point of curve.
+
+ Returns:
+ An iterator yielding the control points (four complex values) of the
+ subcurves.
+ """
+ # Hand-coded special-cases
+ if n == 2:
+ return iter(split_cubic_into_two(p0, p1, p2, p3))
+ if n == 3:
+ return iter(split_cubic_into_three(p0, p1, p2, p3))
+ if n == 4:
+ a, b = split_cubic_into_two(p0, p1, p2, p3)
+ return iter(split_cubic_into_two(*a) + split_cubic_into_two(*b))
+ if n == 6:
+ a, b = split_cubic_into_two(p0, p1, p2, p3)
+ return iter(split_cubic_into_three(*a) + split_cubic_into_three(*b))
+
+ return _split_cubic_into_n_gen(p0,p1,p2,p3,n)
+
+
+@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex, n=cython.int)
+@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
+@cython.locals(dt=cython.double, delta_2=cython.double, delta_3=cython.double, i=cython.int)
+@cython.locals(a1=cython.complex, b1=cython.complex, c1=cython.complex, d1=cython.complex)
+def _split_cubic_into_n_gen(p0, p1, p2, p3, n):
+ a, b, c, d = calc_cubic_parameters(p0, p1, p2, p3)
+ dt = 1 / n
+ delta_2 = dt * dt
+ delta_3 = dt * delta_2
+ for i in range(n):
+ t1 = i * dt
+ t1_2 = t1 * t1
+ # calc new a, b, c and d
+ a1 = a * delta_3
+ b1 = (3*a*t1 + b) * delta_2
+ c1 = (2*b*t1 + c + 3*a*t1_2) * dt
+ d1 = a*t1*t1_2 + b*t1_2 + c*t1 + d
+ yield calc_cubic_points(a1, b1, c1, d1)
+
+
+@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex)
+@cython.locals(mid=cython.complex, deriv3=cython.complex)
+def split_cubic_into_two(p0, p1, p2, p3):
+ """Split a cubic Bezier into two equal parts.
+
+ Splits the curve into two equal parts at t = 0.5
+
+ Args:
+ p0 (complex): Start point of curve.
+ p1 (complex): First handle of curve.
+ p2 (complex): Second handle of curve.
+ p3 (complex): End point of curve.
+
+ Returns:
+ tuple: Two cubic Beziers (each expressed as a tuple of four complex
+ values).
+ """
+ mid = (p0 + 3 * (p1 + p2) + p3) * .125
+ deriv3 = (p3 + p2 - p1 - p0) * .125
+ return ((p0, (p0 + p1) * .5, mid - deriv3, mid),
+ (mid, mid + deriv3, (p2 + p3) * .5, p3))
+
+
+@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex, _27=cython.double)
+@cython.locals(mid1=cython.complex, deriv1=cython.complex, mid2=cython.complex, deriv2=cython.complex)
+def split_cubic_into_three(p0, p1, p2, p3, _27=1/27):
+ """Split a cubic Bezier into three equal parts.
+
+ Splits the curve into three equal parts at t = 1/3 and t = 2/3
+
+ Args:
+ p0 (complex): Start point of curve.
+ p1 (complex): First handle of curve.
+ p2 (complex): Second handle of curve.
+ p3 (complex): End point of curve.
+
+ Returns:
+ tuple: Three cubic Beziers (each expressed as a tuple of four complex
+ values).
+ """
+ # we define 1/27 as a keyword argument so that it will be evaluated only
+ # once but still in the scope of this function
+ mid1 = (8*p0 + 12*p1 + 6*p2 + p3) * _27
+ deriv1 = (p3 + 3*p2 - 4*p0) * _27
+ mid2 = (p0 + 6*p1 + 12*p2 + 8*p3) * _27
+ deriv2 = (4*p3 - 3*p1 - p0) * _27
+ return ((p0, (2*p0 + p1) / 3.0, mid1 - deriv1, mid1),
+ (mid1, mid1 + deriv1, mid2 - deriv2, mid2),
+ (mid2, mid2 + deriv2, (p2 + 2*p3) / 3.0, p3))
+
+
+@cython.returns(cython.complex)
+@cython.locals(t=cython.double, p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex)
+@cython.locals(_p1=cython.complex, _p2=cython.complex)
+def cubic_approx_control(t, p0, p1, p2, p3):
+ """Approximate a cubic Bezier using a quadratic one.
+
+ Args:
+ t (double): Position of control point.
+ p0 (complex): Start point of curve.
+ p1 (complex): First handle of curve.
+ p2 (complex): Second handle of curve.
+ p3 (complex): End point of curve.
+
+ Returns:
+ complex: Location of candidate control point on quadratic curve.
+ """
+ _p1 = p0 + (p1 - p0) * 1.5
+ _p2 = p3 + (p2 - p3) * 1.5
+ return _p1 + (_p2 - _p1) * t
+
+
+@cython.returns(cython.complex)
+@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
+@cython.locals(ab=cython.complex, cd=cython.complex, p=cython.complex, h=cython.double)
+def calc_intersect(a, b, c, d):
+ """Calculate the intersection of two lines.
+
+ Args:
+ a (complex): Start point of first line.
+ b (complex): End point of first line.
+ c (complex): Start point of second line.
+ d (complex): End point of second line.
+
+ Returns:
+ complex: Location of intersection if one present, ``complex(NaN,NaN)``
+ if no intersection was found.
+ """
+ ab = b - a
+ cd = d - c
+ p = ab * 1j
+ try:
+ h = dot(p, a - c) / dot(p, cd)
+ except ZeroDivisionError:
+ return complex(NAN, NAN)
+ return c + cd * h
+
+
+@cython.cfunc
+@cython.returns(cython.int)
+@cython.locals(tolerance=cython.double, p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex)
+@cython.locals(mid=cython.complex, deriv3=cython.complex)
+def cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance):
+ """Check if a cubic Bezier lies within a given distance of the origin.
+
+ "Origin" means *the* origin (0,0), not the start of the curve. Note that no
+ checks are made on the start and end positions of the curve; this function
+ only checks the inside of the curve.
+
+ Args:
+ p0 (complex): Start point of curve.
+ p1 (complex): First handle of curve.
+ p2 (complex): Second handle of curve.
+ p3 (complex): End point of curve.
+ tolerance (double): Distance from origin.
+
+ Returns:
+ bool: True if the cubic Bezier ``p`` entirely lies within a distance
+ ``tolerance`` of the origin, False otherwise.
+ """
+ # First check p2 then p1, as p2 has higher error early on.
+ if abs(p2) <= tolerance and abs(p1) <= tolerance:
+ return True
+
+ # Split.
+ mid = (p0 + 3 * (p1 + p2) + p3) * .125
+ if abs(mid) > tolerance:
+ return False
+ deriv3 = (p3 + p2 - p1 - p0) * .125
+ return (cubic_farthest_fit_inside(p0, (p0+p1)*.5, mid-deriv3, mid, tolerance) and
+ cubic_farthest_fit_inside(mid, mid+deriv3, (p2+p3)*.5, p3, tolerance))
+
+
+@cython.cfunc
+@cython.locals(tolerance=cython.double, _2_3=cython.double)
+@cython.locals(q1=cython.complex, c0=cython.complex, c1=cython.complex, c2=cython.complex, c3=cython.complex)
+def cubic_approx_quadratic(cubic, tolerance, _2_3=2/3):
+ """Approximate a cubic Bezier with a single quadratic within a given tolerance.
+
+ Args:
+ cubic (sequence): Four complex numbers representing control points of
+ the cubic Bezier curve.
+ tolerance (double): Permitted deviation from the original curve.
+
+ Returns:
+ Three complex numbers representing control points of the quadratic
+ curve if it fits within the given tolerance, or ``None`` if no suitable
+ curve could be calculated.
+ """
+ # we define 2/3 as a keyword argument so that it will be evaluated only
+ # once but still in the scope of this function
+
+ q1 = calc_intersect(*cubic)
+ if math.isnan(q1.imag):
+ return None
+ c0 = cubic[0]
+ c3 = cubic[3]
+ c1 = c0 + (q1 - c0) * _2_3
+ c2 = c3 + (q1 - c3) * _2_3
+ if not cubic_farthest_fit_inside(0,
+ c1 - cubic[1],
+ c2 - cubic[2],
+ 0, tolerance):
+ return None
+ return c0, q1, c3
+
+
+@cython.cfunc
+@cython.locals(n=cython.int, tolerance=cython.double, _2_3=cython.double)
+@cython.locals(i=cython.int)
+@cython.locals(c0=cython.complex, c1=cython.complex, c2=cython.complex, c3=cython.complex)
+@cython.locals(q0=cython.complex, q1=cython.complex, next_q1=cython.complex, q2=cython.complex, d1=cython.complex)
+def cubic_approx_spline(cubic, n, tolerance, _2_3=2/3):
+ """Approximate a cubic Bezier curve with a spline of n quadratics.
+
+ Args:
+ cubic (sequence): Four complex numbers representing control points of
+ the cubic Bezier curve.
+ n (int): Number of quadratic Bezier curves in the spline.
+ tolerance (double): Permitted deviation from the original curve.
+
+ Returns:
+ A list of ``n+2`` complex numbers, representing control points of the
+ quadratic spline if it fits within the given tolerance, or ``None`` if
+ no suitable spline could be calculated.
+ """
+ # we define 2/3 as a keyword argument so that it will be evaluated only
+ # once but still in the scope of this function
+
+ if n == 1:
+ return cubic_approx_quadratic(cubic, tolerance)
+
+ cubics = split_cubic_into_n_iter(cubic[0], cubic[1], cubic[2], cubic[3], n)
+
+ # calculate the spline of quadratics and check errors at the same time.
+ next_cubic = next(cubics)
+ next_q1 = cubic_approx_control(0, *next_cubic)
+ q2 = cubic[0]
+ d1 = 0j
+ spline = [cubic[0], next_q1]
+ for i in range(1, n+1):
+
+ # Current cubic to convert
+ c0, c1, c2, c3 = next_cubic
+
+ # Current quadratic approximation of current cubic
+ q0 = q2
+ q1 = next_q1
+ if i < n:
+ next_cubic = next(cubics)
+ next_q1 = cubic_approx_control(i / (n-1), *next_cubic)
+ spline.append(next_q1)
+ q2 = (q1 + next_q1) * .5
+ else:
+ q2 = c3
+
+ # End-point deltas
+ d0 = d1
+ d1 = q2 - c3
+
+ if (abs(d1) > tolerance or
+ not cubic_farthest_fit_inside(d0,
+ q0 + (q1 - q0) * _2_3 - c1,
+ q2 + (q1 - q2) * _2_3 - c2,
+ d1,
+ tolerance)):
+ return None
+ spline.append(cubic[3])
+
+ return spline
+
+
+@cython.locals(max_err=cython.double)
+@cython.locals(n=cython.int)
+def curve_to_quadratic(curve, max_err):
+ """Approximate a cubic Bezier curve with a spline of n quadratics.
+
+ Args:
+ cubic (sequence): Four 2D tuples representing control points of
+ the cubic Bezier curve.
+ max_err (double): Permitted deviation from the original curve.
+
+ Returns:
+ A list of 2D tuples, representing control points of the quadratic
+ spline if it fits within the given tolerance, or ``None`` if no
+ suitable spline could be calculated.
+ """
+
+ curve = [complex(*p) for p in curve]
+
+ for n in range(1, MAX_N + 1):
+ spline = cubic_approx_spline(curve, n, max_err)
+ if spline is not None:
+ # done. go home
+ return [(s.real, s.imag) for s in spline]
+
+ raise ApproxNotFoundError(curve)
+
+
+
+@cython.locals(l=cython.int, last_i=cython.int, i=cython.int)
+def curves_to_quadratic(curves, max_errors):
+ """Return quadratic Bezier splines approximating the input cubic Beziers.
+
+ Args:
+ curves: A sequence of *n* curves, each curve being a sequence of four
+ 2D tuples.
+ max_errors: A sequence of *n* floats representing the maximum permissible
+ deviation from each of the cubic Bezier curves.
+
+ Example::
+
+ >>> curves_to_quadratic( [
+ ... [ (50,50), (100,100), (150,100), (200,50) ],
+ ... [ (75,50), (120,100), (150,75), (200,60) ]
+ ... ], [1,1] )
+ [[(50.0, 50.0), (75.0, 75.0), (125.0, 91.66666666666666), (175.0, 75.0), (200.0, 50.0)], [(75.0, 50.0), (97.5, 75.0), (135.41666666666666, 82.08333333333333), (175.0, 67.5), (200.0, 60.0)]]
+
+ The returned splines have "implied oncurve points" suitable for use in
+ TrueType ``glif`` outlines - i.e. in the first spline returned above,
+ the first quadratic segment runs from (50,50) to
+ ( (75 + 125)/2 , (120 + 91.666..)/2 ) = (100, 83.333...).
+
+ Returns:
+ A list of splines, each spline being a list of 2D tuples.
+
+ Raises:
+ fontTools.cu2qu.Errors.ApproxNotFoundError: if no suitable approximation
+ can be found for all curves with the given parameters.
+ """
+
+ curves = [[complex(*p) for p in curve] for curve in curves]
+ assert len(max_errors) == len(curves)
+
+ l = len(curves)
+ splines = [None] * l
+ last_i = i = 0
+ n = 1
+ while True:
+ spline = cubic_approx_spline(curves[i], n, max_errors[i])
+ if spline is None:
+ if n == MAX_N:
+ break
+ n += 1
+ last_i = i
+ continue
+ splines[i] = spline
+ i = (i + 1) % l
+ if i == last_i:
+ # done. go home
+ return [[(s.real, s.imag) for s in spline] for spline in splines]
+
+ raise ApproxNotFoundError(curves)
+
+
+if __name__ == '__main__':
+ import random
+ import timeit
+
+ MAX_ERR = 5
+
+ def generate_curve():
+ return [
+ tuple(float(random.randint(0, 2048)) for coord in range(2))
+ for point in range(4)]
+
+ def setup_curve_to_quadratic():
+ return generate_curve(), MAX_ERR
+
+ def setup_curves_to_quadratic():
+ num_curves = 3
+ return (
+ [generate_curve() for curve in range(num_curves)],
+ [MAX_ERR] * num_curves)
+
+ def run_benchmark(
+ benchmark_module, module, function, setup_suffix='', repeat=5, number=1000):
+ setup_func = 'setup_' + function
+ if setup_suffix:
+ print('%s with %s:' % (function, setup_suffix), end='')
+ setup_func += '_' + setup_suffix
+ else:
+ print('%s:' % function, end='')
+
+ def wrapper(function, setup_func):
+ function = globals()[function]
+ setup_func = globals()[setup_func]
+ def wrapped():
+ return function(*setup_func())
+ return wrapped
+ results = timeit.repeat(wrapper(function, setup_func), repeat=repeat, number=number)
+ print('\t%5.1fus' % (min(results) * 1000000. / number))
+
+ def main():
+ run_benchmark('cu2qu.benchmark', 'cu2qu', 'curve_to_quadratic')
+ run_benchmark('cu2qu.benchmark', 'cu2qu', 'curves_to_quadratic')
+
+ random.seed(1)
+ main()
diff --git a/Lib/fontTools/cu2qu/errors.py b/Lib/fontTools/cu2qu/errors.py
new file mode 100644
index 00000000..74c4c227
--- /dev/null
+++ b/Lib/fontTools/cu2qu/errors.py
@@ -0,0 +1,76 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Error(Exception):
+ """Base Cu2Qu exception class for all other errors."""
+
+
+class ApproxNotFoundError(Error):
+ def __init__(self, curve):
+ message = "no approximation found: %s" % curve
+ super().__init__(message)
+ self.curve = curve
+
+
+class UnequalZipLengthsError(Error):
+ pass
+
+
+class IncompatibleGlyphsError(Error):
+ def __init__(self, glyphs):
+ assert len(glyphs) > 1
+ self.glyphs = glyphs
+ names = set(repr(g.name) for g in glyphs)
+ if len(names) > 1:
+ self.combined_name = "{%s}" % ", ".join(sorted(names))
+ else:
+ self.combined_name = names.pop()
+
+ def __repr__(self):
+ return "<%s %s>" % (type(self).__name__, self.combined_name)
+
+
+class IncompatibleSegmentNumberError(IncompatibleGlyphsError):
+ def __str__(self):
+ return "Glyphs named %s have different number of segments" % (
+ self.combined_name
+ )
+
+
+class IncompatibleSegmentTypesError(IncompatibleGlyphsError):
+ def __init__(self, glyphs, segments):
+ IncompatibleGlyphsError.__init__(self, glyphs)
+ self.segments = segments
+
+ def __str__(self):
+ lines = []
+ ndigits = len(str(max(self.segments)))
+ for i, tags in sorted(self.segments.items()):
+ lines.append(
+ "%s: (%s)" % (str(i).rjust(ndigits), ", ".join(repr(t) for t in tags))
+ )
+ return "Glyphs named %s have incompatible segment types:\n %s" % (
+ self.combined_name,
+ "\n ".join(lines),
+ )
+
+
+class IncompatibleFontsError(Error):
+ def __init__(self, glyph_errors):
+ self.glyph_errors = glyph_errors
+
+ def __str__(self):
+ return "fonts contains incompatible glyphs: %s" % (
+ ", ".join(repr(g) for g in sorted(self.glyph_errors.keys()))
+ )
diff --git a/Lib/fontTools/cu2qu/ufo.py b/Lib/fontTools/cu2qu/ufo.py
new file mode 100644
index 00000000..447de7bb
--- /dev/null
+++ b/Lib/fontTools/cu2qu/ufo.py
@@ -0,0 +1,324 @@
+# Copyright 2015 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Converts cubic bezier curves to quadratic splines.
+
+Conversion is performed such that the quadratic splines keep the same end-curve
+tangents as the original cubics. The approach is iterative, increasing the
+number of segments for a spline until the error gets below a bound.
+
+Respective curves from multiple fonts will be converted at once to ensure that
+the resulting splines are interpolation-compatible.
+"""
+
+import logging
+from fontTools.pens.basePen import AbstractPen
+from fontTools.pens.pointPen import PointToSegmentPen
+from fontTools.pens.reverseContourPen import ReverseContourPen
+
+from . import curves_to_quadratic
+from .errors import (
+ UnequalZipLengthsError, IncompatibleSegmentNumberError,
+ IncompatibleSegmentTypesError, IncompatibleGlyphsError,
+ IncompatibleFontsError)
+
+
+__all__ = ['fonts_to_quadratic', 'font_to_quadratic']
+
+# The default approximation error below is a relative value (1/1000 of the EM square).
+# Later on, we convert it to absolute font units by multiplying it by a font's UPEM
+# (see fonts_to_quadratic).
+DEFAULT_MAX_ERR = 0.001
+CURVE_TYPE_LIB_KEY = "com.github.googlei18n.cu2qu.curve_type"
+
+logger = logging.getLogger(__name__)
+
+
+_zip = zip
+def zip(*args):
+ """Ensure each argument to zip has the same length. Also make sure a list is
+ returned for python 2/3 compatibility.
+ """
+
+ if len(set(len(a) for a in args)) != 1:
+ raise UnequalZipLengthsError(*args)
+ return list(_zip(*args))
+
+
+class GetSegmentsPen(AbstractPen):
+ """Pen to collect segments into lists of points for conversion.
+
+ Curves always include their initial on-curve point, so some points are
+ duplicated between segments.
+ """
+
+ def __init__(self):
+ self._last_pt = None
+ self.segments = []
+
+ def _add_segment(self, tag, *args):
+ if tag in ['move', 'line', 'qcurve', 'curve']:
+ self._last_pt = args[-1]
+ self.segments.append((tag, args))
+
+ def moveTo(self, pt):
+ self._add_segment('move', pt)
+
+ def lineTo(self, pt):
+ self._add_segment('line', pt)
+
+ def qCurveTo(self, *points):
+ self._add_segment('qcurve', self._last_pt, *points)
+
+ def curveTo(self, *points):
+ self._add_segment('curve', self._last_pt, *points)
+
+ def closePath(self):
+ self._add_segment('close')
+
+ def endPath(self):
+ self._add_segment('end')
+
+ def addComponent(self, glyphName, transformation):
+ pass
+
+
+def _get_segments(glyph):
+ """Get a glyph's segments as extracted by GetSegmentsPen."""
+
+ pen = GetSegmentsPen()
+ # glyph.draw(pen)
+ # We can't simply draw the glyph with the pen, but we must initialize the
+ # PointToSegmentPen explicitly with outputImpliedClosingLine=True.
+ # By default PointToSegmentPen does not outputImpliedClosingLine -- unless
+ # last and first point on closed contour are duplicated. Because we are
+ # converting multiple glyphs at the same time, we want to make sure
+ # this function returns the same number of segments, whether or not
+ # the last and first point overlap.
+ # https://github.com/googlefonts/fontmake/issues/572
+ # https://github.com/fonttools/fonttools/pull/1720
+ pointPen = PointToSegmentPen(pen, outputImpliedClosingLine=True)
+ glyph.drawPoints(pointPen)
+ return pen.segments
+
+
+def _set_segments(glyph, segments, reverse_direction):
+ """Draw segments as extracted by GetSegmentsPen back to a glyph."""
+
+ glyph.clearContours()
+ pen = glyph.getPen()
+ if reverse_direction:
+ pen = ReverseContourPen(pen)
+ for tag, args in segments:
+ if tag == 'move':
+ pen.moveTo(*args)
+ elif tag == 'line':
+ pen.lineTo(*args)
+ elif tag == 'curve':
+ pen.curveTo(*args[1:])
+ elif tag == 'qcurve':
+ pen.qCurveTo(*args[1:])
+ elif tag == 'close':
+ pen.closePath()
+ elif tag == 'end':
+ pen.endPath()
+ else:
+ raise AssertionError('Unhandled segment type "%s"' % tag)
+
+
+def _segments_to_quadratic(segments, max_err, stats):
+ """Return quadratic approximations of cubic segments."""
+
+ assert all(s[0] == 'curve' for s in segments), 'Non-cubic given to convert'
+
+ new_points = curves_to_quadratic([s[1] for s in segments], max_err)
+ n = len(new_points[0])
+ assert all(len(s) == n for s in new_points[1:]), 'Converted incompatibly'
+
+ spline_length = str(n - 2)
+ stats[spline_length] = stats.get(spline_length, 0) + 1
+
+ return [('qcurve', p) for p in new_points]
+
+
+def _glyphs_to_quadratic(glyphs, max_err, reverse_direction, stats):
+ """Do the actual conversion of a set of compatible glyphs, after arguments
+ have been set up.
+
+ Return True if the glyphs were modified, else return False.
+ """
+
+ try:
+ segments_by_location = zip(*[_get_segments(g) for g in glyphs])
+ except UnequalZipLengthsError:
+ raise IncompatibleSegmentNumberError(glyphs)
+ if not any(segments_by_location):
+ return False
+
+ # always modify input glyphs if reverse_direction is True
+ glyphs_modified = reverse_direction
+
+ new_segments_by_location = []
+ incompatible = {}
+ for i, segments in enumerate(segments_by_location):
+ tag = segments[0][0]
+ if not all(s[0] == tag for s in segments[1:]):
+ incompatible[i] = [s[0] for s in segments]
+ elif tag == 'curve':
+ segments = _segments_to_quadratic(segments, max_err, stats)
+ glyphs_modified = True
+ new_segments_by_location.append(segments)
+
+ if glyphs_modified:
+ new_segments_by_glyph = zip(*new_segments_by_location)
+ for glyph, new_segments in zip(glyphs, new_segments_by_glyph):
+ _set_segments(glyph, new_segments, reverse_direction)
+
+ if incompatible:
+ raise IncompatibleSegmentTypesError(glyphs, segments=incompatible)
+ return glyphs_modified
+
+
+def glyphs_to_quadratic(
+ glyphs, max_err=None, reverse_direction=False, stats=None):
+ """Convert the curves of a set of compatible of glyphs to quadratic.
+
+ All curves will be converted to quadratic at once, ensuring interpolation
+ compatibility. If this is not required, calling glyphs_to_quadratic with one
+ glyph at a time may yield slightly more optimized results.
+
+ Return True if glyphs were modified, else return False.
+
+ Raises IncompatibleGlyphsError if glyphs have non-interpolatable outlines.
+ """
+ if stats is None:
+ stats = {}
+
+ if not max_err:
+ # assume 1000 is the default UPEM
+ max_err = DEFAULT_MAX_ERR * 1000
+
+ if isinstance(max_err, (list, tuple)):
+ max_errors = max_err
+ else:
+ max_errors = [max_err] * len(glyphs)
+ assert len(max_errors) == len(glyphs)
+
+ return _glyphs_to_quadratic(glyphs, max_errors, reverse_direction, stats)
+
+
+def fonts_to_quadratic(
+ fonts, max_err_em=None, max_err=None, reverse_direction=False,
+ stats=None, dump_stats=False, remember_curve_type=True):
+ """Convert the curves of a collection of fonts to quadratic.
+
+ All curves will be converted to quadratic at once, ensuring interpolation
+ compatibility. If this is not required, calling fonts_to_quadratic with one
+ font at a time may yield slightly more optimized results.
+
+ Return True if fonts were modified, else return False.
+
+ By default, cu2qu stores the curve type in the fonts' lib, under a private
+ key "com.github.googlei18n.cu2qu.curve_type", and will not try to convert
+ them again if the curve type is already set to "quadratic".
+ Setting 'remember_curve_type' to False disables this optimization.
+
+ Raises IncompatibleFontsError if same-named glyphs from different fonts
+ have non-interpolatable outlines.
+ """
+
+ if remember_curve_type:
+ curve_types = {f.lib.get(CURVE_TYPE_LIB_KEY, "cubic") for f in fonts}
+ if len(curve_types) == 1:
+ curve_type = next(iter(curve_types))
+ if curve_type == "quadratic":
+ logger.info("Curves already converted to quadratic")
+ return False
+ elif curve_type == "cubic":
+ pass # keep converting
+ else:
+ raise NotImplementedError(curve_type)
+ elif len(curve_types) > 1:
+ # going to crash later if they do differ
+ logger.warning("fonts may contain different curve types")
+
+ if stats is None:
+ stats = {}
+
+ if max_err_em and max_err:
+ raise TypeError('Only one of max_err and max_err_em can be specified.')
+ if not (max_err_em or max_err):
+ max_err_em = DEFAULT_MAX_ERR
+
+ if isinstance(max_err, (list, tuple)):
+ assert len(max_err) == len(fonts)
+ max_errors = max_err
+ elif max_err:
+ max_errors = [max_err] * len(fonts)
+
+ if isinstance(max_err_em, (list, tuple)):
+ assert len(fonts) == len(max_err_em)
+ max_errors = [f.info.unitsPerEm * e
+ for f, e in zip(fonts, max_err_em)]
+ elif max_err_em:
+ max_errors = [f.info.unitsPerEm * max_err_em for f in fonts]
+
+ modified = False
+ glyph_errors = {}
+ for name in set().union(*(f.keys() for f in fonts)):
+ glyphs = []
+ cur_max_errors = []
+ for font, error in zip(fonts, max_errors):
+ if name in font:
+ glyphs.append(font[name])
+ cur_max_errors.append(error)
+ try:
+ modified |= _glyphs_to_quadratic(
+ glyphs, cur_max_errors, reverse_direction, stats)
+ except IncompatibleGlyphsError as exc:
+ logger.error(exc)
+ glyph_errors[name] = exc
+
+ if glyph_errors:
+ raise IncompatibleFontsError(glyph_errors)
+
+ if modified and dump_stats:
+ spline_lengths = sorted(stats.keys())
+ logger.info('New spline lengths: %s' % (', '.join(
+ '%s: %d' % (l, stats[l]) for l in spline_lengths)))
+
+ if remember_curve_type:
+ for font in fonts:
+ curve_type = font.lib.get(CURVE_TYPE_LIB_KEY, "cubic")
+ if curve_type != "quadratic":
+ font.lib[CURVE_TYPE_LIB_KEY] = "quadratic"
+ modified = True
+ return modified
+
+
+def glyph_to_quadratic(glyph, **kwargs):
+ """Convenience wrapper around glyphs_to_quadratic, for just one glyph.
+ Return True if the glyph was modified, else return False.
+ """
+
+ return glyphs_to_quadratic([glyph], **kwargs)
+
+
+def font_to_quadratic(font, **kwargs):
+ """Convenience wrapper around fonts_to_quadratic, for just one font.
+ Return True if the font was modified, else return False.
+ """
+
+ return fonts_to_quadratic([font], **kwargs)
diff --git a/Lib/fontTools/designspaceLib/__init__.py b/Lib/fontTools/designspaceLib/__init__.py
index e40f5edf..9ea22fe6 100644
--- a/Lib/fontTools/designspaceLib/__init__.py
+++ b/Lib/fontTools/designspaceLib/__init__.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes, tostr
from fontTools.misc.loggingTools import LogMixin
import collections
+from io import BytesIO, StringIO
import os
import posixpath
from fontTools.misc import etree as ET
@@ -101,14 +101,32 @@ class SourceDescriptor(SimpleDescriptor):
'mutedGlyphNames',
'familyName', 'styleName']
- def __init__(self):
- self.filename = None
+ def __init__(
+ self,
+ *,
+ filename=None,
+ path=None,
+ font=None,
+ name=None,
+ location=None,
+ layerName=None,
+ familyName=None,
+ styleName=None,
+ copyLib=False,
+ copyInfo=False,
+ copyGroups=False,
+ copyFeatures=False,
+ muteKerning=False,
+ muteInfo=False,
+ mutedGlyphNames=None,
+ ):
+ self.filename = filename
"""The original path as found in the document."""
- self.path = None
+ self.path = path
"""The absolute path, calculated from filename."""
- self.font = None
+ self.font = font
"""Any Python object. Optional. Points to a representation of this
source font that is loaded in memory, as a Python object (e.g. a
``defcon.Font`` or a ``fontTools.ttFont.TTFont``).
@@ -120,18 +138,19 @@ class SourceDescriptor(SimpleDescriptor):
this field to the disk and make ```filename`` point to that.
"""
- self.name = None
- self.location = None
- self.layerName = None
- self.copyLib = False
- self.copyInfo = False
- self.copyGroups = False
- self.copyFeatures = False
- self.muteKerning = False
- self.muteInfo = False
- self.mutedGlyphNames = []
- self.familyName = None
- self.styleName = None
+ self.name = name
+ self.location = location
+ self.layerName = layerName
+ self.familyName = familyName
+ self.styleName = styleName
+
+ self.copyLib = copyLib
+ self.copyInfo = copyInfo
+ self.copyGroups = copyGroups
+ self.copyFeatures = copyFeatures
+ self.muteKerning = muteKerning
+ self.muteInfo = muteInfo
+ self.mutedGlyphNames = mutedGlyphNames or []
path = posixpath_property("_path")
filename = posixpath_property("_filename")
@@ -153,10 +172,12 @@ class RuleDescriptor(SimpleDescriptor):
"""
_attrs = ['name', 'conditionSets', 'subs'] # what do we need here
- def __init__(self):
- self.name = None
- self.conditionSets = [] # list of list of dict(name='aaaa', minimum=0, maximum=1000)
- self.subs = [] # list of substitutions stored as tuples of glyphnames ("a", "a.alt")
+ def __init__(self, *, name=None, conditionSets=None, subs=None):
+ self.name = name
+ # list of lists of dict(name='aaaa', minimum=0, maximum=1000)
+ self.conditionSets = conditionSets or []
+ # list of substitutions stored as tuples of glyphnames ("a", "a.alt")
+ self.subs = subs or []
def evaluateRule(rule, location):
@@ -183,7 +204,7 @@ def evaluateConditions(conditions, location):
def processRules(rules, location, glyphNames):
- """ Apply these rules at this location to these glyphnames.minimum
+ """ Apply these rules at this location to these glyphnames
- rule order matters
"""
newNames = []
@@ -220,50 +241,75 @@ class InstanceDescriptor(SimpleDescriptor):
'info',
'lib']
- def __init__(self):
- self.filename = None # the original path as found in the document
- self.path = None # the absolute path, calculated from filename
- self.name = None
- self.location = None
- self.familyName = None
- self.styleName = None
- self.postScriptFontName = None
- self.styleMapFamilyName = None
- self.styleMapStyleName = None
- self.localisedStyleName = {}
- self.localisedFamilyName = {}
- self.localisedStyleMapStyleName = {}
- self.localisedStyleMapFamilyName = {}
- self.glyphs = {}
- self.kerning = True
- self.info = True
-
- self.lib = {}
+ def __init__(
+ self,
+ *,
+ filename=None,
+ path=None,
+ font=None,
+ name=None,
+ location=None,
+ familyName=None,
+ styleName=None,
+ postScriptFontName=None,
+ styleMapFamilyName=None,
+ styleMapStyleName=None,
+ localisedFamilyName=None,
+ localisedStyleName=None,
+ localisedStyleMapFamilyName=None,
+ localisedStyleMapStyleName=None,
+ glyphs=None,
+ kerning=True,
+ info=True,
+ lib=None,
+ ):
+ # the original path as found in the document
+ self.filename = filename
+ # the absolute path, calculated from filename
+ self.path = path
+ # Same as in SourceDescriptor.
+ self.font = font
+ self.name = name
+ self.location = location
+ self.familyName = familyName
+ self.styleName = styleName
+ self.postScriptFontName = postScriptFontName
+ self.styleMapFamilyName = styleMapFamilyName
+ self.styleMapStyleName = styleMapStyleName
+ self.localisedFamilyName = localisedFamilyName or {}
+ self.localisedStyleName = localisedStyleName or {}
+ self.localisedStyleMapFamilyName = localisedStyleMapFamilyName or {}
+ self.localisedStyleMapStyleName = localisedStyleMapStyleName or {}
+ self.glyphs = glyphs or {}
+ self.kerning = kerning
+ self.info = info
+
+ self.lib = lib or {}
"""Custom data associated with this instance."""
path = posixpath_property("_path")
filename = posixpath_property("_filename")
def setStyleName(self, styleName, languageCode="en"):
- self.localisedStyleName[languageCode] = tounicode(styleName)
+ self.localisedStyleName[languageCode] = tostr(styleName)
def getStyleName(self, languageCode="en"):
return self.localisedStyleName.get(languageCode)
def setFamilyName(self, familyName, languageCode="en"):
- self.localisedFamilyName[languageCode] = tounicode(familyName)
+ self.localisedFamilyName[languageCode] = tostr(familyName)
def getFamilyName(self, languageCode="en"):
return self.localisedFamilyName.get(languageCode)
def setStyleMapStyleName(self, styleMapStyleName, languageCode="en"):
- self.localisedStyleMapStyleName[languageCode] = tounicode(styleMapStyleName)
+ self.localisedStyleMapStyleName[languageCode] = tostr(styleMapStyleName)
def getStyleMapStyleName(self, languageCode="en"):
return self.localisedStyleMapStyleName.get(languageCode)
def setStyleMapFamilyName(self, styleMapFamilyName, languageCode="en"):
- self.localisedStyleMapFamilyName[languageCode] = tounicode(styleMapFamilyName)
+ self.localisedStyleMapFamilyName[languageCode] = tostr(styleMapFamilyName)
def getStyleMapFamilyName(self, languageCode="en"):
return self.localisedStyleMapFamilyName.get(languageCode)
@@ -294,15 +340,29 @@ class AxisDescriptor(SimpleDescriptor):
flavor = "axis"
_attrs = ['tag', 'name', 'maximum', 'minimum', 'default', 'map']
- def __init__(self):
- self.tag = None # opentype tag for this axis
- self.name = None # name of the axis used in locations
- self.labelNames = {} # names for UI purposes, if this is not a standard axis,
- self.minimum = None
- self.maximum = None
- self.default = None
- self.hidden = False
- self.map = []
+ def __init__(
+ self,
+ *,
+ tag=None,
+ name=None,
+ labelNames=None,
+ minimum=None,
+ default=None,
+ maximum=None,
+ hidden=False,
+ map=None,
+ ):
+ # opentype tag for this axis
+ self.tag = tag
+ # name of the axis used in locations
+ self.name = name
+ # names for UI purposes, if this is not a standard axis,
+ self.labelNames = labelNames or {}
+ self.minimum = minimum
+ self.maximum = maximum
+ self.default = default
+ self.hidden = hidden
+ self.map = map or []
def serialize(self):
# output to a dict, used in testing
@@ -358,7 +418,7 @@ class BaseDocWriter(object):
def __init__(self, documentPath, documentObject):
self.path = documentPath
self.documentObject = documentObject
- self.documentVersion = "4.0"
+ self.documentVersion = "4.1"
self.root = ET.Element("designspace")
self.root.attrib['format'] = self.documentVersion
self._axes = [] # for use by the writer only
@@ -371,7 +431,11 @@ class BaseDocWriter(object):
self._addAxis(axisObject)
if self.documentObject.rules:
- self.root.append(ET.Element("rules"))
+ if getattr(self.documentObject, "rulesProcessingLast", False):
+ attributes = {"processing": "last"}
+ else:
+ attributes = {}
+ self.root.append(ET.Element("rules", attributes))
for ruleObject in self.documentObject.rules:
self._addRule(ruleObject)
@@ -675,6 +739,14 @@ class BaseDocReader(LogMixin):
def readRules(self):
# we also need to read any conditions that are outside of a condition set.
rules = []
+ rulesElement = self.root.find(".rules")
+ if rulesElement is not None:
+ processingValue = rulesElement.attrib.get("processing", "first")
+ if processingValue not in {"first", "last"}:
+ raise DesignSpaceDocumentError(
+ "<rules> processing attribute value is not valid: %r, "
+ "expected 'first' or 'last'" % processingValue)
+ self.documentObject.rulesProcessingLast = processingValue == "last"
for ruleElement in self.root.findall(".rules/rule"):
ruleObject = self.ruleDescriptorClass()
ruleName = ruleObject.name = ruleElement.attrib.get("name")
@@ -752,7 +824,7 @@ class BaseDocReader(LogMixin):
# '{http://www.w3.org/XML/1998/namespace}lang'
for key, lang in labelNameElement.items():
if key == XML_LANG:
- axisObject.labelNames[lang] = tounicode(labelNameElement.text)
+ axisObject.labelNames[lang] = tostr(labelNameElement.text)
self.documentObject.axes.append(axisObject)
self.axisDefaults[axisObject.name] = axisObject.default
@@ -996,6 +1068,7 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
self.instances = []
self.axes = []
self.rules = []
+ self.rulesProcessingLast = False
self.default = None # name of the default master
self.lib = {}
@@ -1027,10 +1100,10 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
return self
def tostring(self, encoding=None):
- if encoding is unicode or (
+ if encoding is str or (
encoding is not None and encoding.lower() == "unicode"
):
- f = UnicodeIO()
+ f = StringIO()
xml_declaration = False
elif encoding is None or encoding == "utf-8":
f = BytesIO()
@@ -1116,15 +1189,35 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
def addSource(self, sourceDescriptor):
self.sources.append(sourceDescriptor)
+ def addSourceDescriptor(self, **kwargs):
+ source = self.writerClass.sourceDescriptorClass(**kwargs)
+ self.addSource(source)
+ return source
+
def addInstance(self, instanceDescriptor):
self.instances.append(instanceDescriptor)
+ def addInstanceDescriptor(self, **kwargs):
+ instance = self.writerClass.instanceDescriptorClass(**kwargs)
+ self.addInstance(instance)
+ return instance
+
def addAxis(self, axisDescriptor):
self.axes.append(axisDescriptor)
+ def addAxisDescriptor(self, **kwargs):
+ axis = self.writerClass.axisDescriptorClass(**kwargs)
+ self.addAxis(axis)
+ return axis
+
def addRule(self, ruleDescriptor):
self.rules.append(ruleDescriptor)
+ def addRuleDescriptor(self, **kwargs):
+ rule = self.writerClass.ruleDescriptorClass(**kwargs)
+ self.addRule(rule)
+ return rule
+
def newDefaultLocation(self):
"""Return default location in design space."""
# Without OrderedDict, output XML would be non-deterministic.
diff --git a/Lib/fontTools/encodings/MacRoman.py b/Lib/fontTools/encodings/MacRoman.py
index 43c58ebb..25232d38 100644
--- a/Lib/fontTools/encodings/MacRoman.py
+++ b/Lib/fontTools/encodings/MacRoman.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
MacRoman = [
'NUL', 'Eth', 'eth', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Yacute',
'yacute', 'HT', 'LF', 'Thorn', 'thorn', 'CR', 'Zcaron', 'zcaron', 'DLE', 'DC1',
diff --git a/Lib/fontTools/encodings/StandardEncoding.py b/Lib/fontTools/encodings/StandardEncoding.py
index dc01ef8f..810b2a09 100644
--- a/Lib/fontTools/encodings/StandardEncoding.py
+++ b/Lib/fontTools/encodings/StandardEncoding.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
StandardEncoding = [
'.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
'.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
diff --git a/Lib/fontTools/encodings/__init__.py b/Lib/fontTools/encodings/__init__.py
index 3f9abc96..156cb232 100644
--- a/Lib/fontTools/encodings/__init__.py
+++ b/Lib/fontTools/encodings/__init__.py
@@ -1,4 +1 @@
"""Empty __init__.py file to signal Python this directory is a package."""
-
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
diff --git a/Lib/fontTools/encodings/codecs.py b/Lib/fontTools/encodings/codecs.py
index 5e401f06..3b1a8256 100644
--- a/Lib/fontTools/encodings/codecs.py
+++ b/Lib/fontTools/encodings/codecs.py
@@ -1,8 +1,6 @@
"""Extend the Python codecs module with a few encodings that are used in OpenType (name table)
but missing from Python. See https://github.com/fonttools/fonttools/issues/236 for details."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import codecs
import encodings
@@ -17,43 +15,29 @@ class ExtendCodec(codecs.Codec):
self.info = codecs.CodecInfo(name=self.name, encode=self.encode, decode=self.decode)
codecs.register_error(name, self.error)
- def encode(self, input, errors='strict'):
- assert errors == 'strict'
- #return codecs.encode(input, self.base_encoding, self.name), len(input)
-
- # The above line could totally be all we needed, relying on the error
- # handling to replace the unencodable Unicode characters with our extended
- # byte sequences.
- #
- # However, there seems to be a design bug in Python (probably intentional):
- # the error handler for encoding is supposed to return a **Unicode** character,
- # that then needs to be encodable itself... Ugh.
- #
- # So we implement what codecs.encode() should have been doing: which is expect
- # error handler to return bytes() to be added to the output.
- #
- # This seems to have been fixed in Python 3.3. We should try using that and
- # use fallback only if that failed.
- # https://docs.python.org/3.3/library/codecs.html#codecs.register_error
-
+ def _map(self, mapper, output_type, exc_type, input, errors):
+ base_error_handler = codecs.lookup_error(errors)
length = len(input)
- out = b''
+ out = output_type()
while input:
+ # first try to use self.error as the error handler
try:
- part = codecs.encode(input, self.base_encoding)
+ part = mapper(input, self.base_encoding, errors=self.name)
out += part
- input = '' # All converted
- except UnicodeEncodeError as e:
- # Convert the correct part
- out += codecs.encode(input[:e.start], self.base_encoding)
- replacement, pos = self.error(e)
+ break # All converted
+ except exc_type as e:
+ # else convert the correct part, handle error as requested and continue
+ out += mapper(input[:e.start], self.base_encoding, self.name)
+ replacement, pos = base_error_handler(e)
out += replacement
input = input[pos:]
return out, length
+ def encode(self, input, errors='strict'):
+ return self._map(codecs.encode, bytes, UnicodeEncodeError, input, errors)
+
def decode(self, input, errors='strict'):
- assert errors == 'strict'
- return codecs.decode(input, self.base_encoding, self.name), len(input)
+ return self._map(codecs.decode, str, UnicodeDecodeError, input, errors)
def error(self, e):
if isinstance(e, UnicodeDecodeError):
@@ -72,35 +56,35 @@ class ExtendCodec(codecs.Codec):
_extended_encodings = {
"x_mac_japanese_ttx": ("shift_jis", {
- b"\xFC": unichr(0x007C),
- b"\x7E": unichr(0x007E),
- b"\x80": unichr(0x005C),
- b"\xA0": unichr(0x00A0),
- b"\xFD": unichr(0x00A9),
- b"\xFE": unichr(0x2122),
- b"\xFF": unichr(0x2026),
+ b"\xFC": chr(0x007C),
+ b"\x7E": chr(0x007E),
+ b"\x80": chr(0x005C),
+ b"\xA0": chr(0x00A0),
+ b"\xFD": chr(0x00A9),
+ b"\xFE": chr(0x2122),
+ b"\xFF": chr(0x2026),
}),
"x_mac_trad_chinese_ttx": ("big5", {
- b"\x80": unichr(0x005C),
- b"\xA0": unichr(0x00A0),
- b"\xFD": unichr(0x00A9),
- b"\xFE": unichr(0x2122),
- b"\xFF": unichr(0x2026),
+ b"\x80": chr(0x005C),
+ b"\xA0": chr(0x00A0),
+ b"\xFD": chr(0x00A9),
+ b"\xFE": chr(0x2122),
+ b"\xFF": chr(0x2026),
}),
"x_mac_korean_ttx": ("euc_kr", {
- b"\x80": unichr(0x00A0),
- b"\x81": unichr(0x20A9),
- b"\x82": unichr(0x2014),
- b"\x83": unichr(0x00A9),
- b"\xFE": unichr(0x2122),
- b"\xFF": unichr(0x2026),
+ b"\x80": chr(0x00A0),
+ b"\x81": chr(0x20A9),
+ b"\x82": chr(0x2014),
+ b"\x83": chr(0x00A9),
+ b"\xFE": chr(0x2122),
+ b"\xFF": chr(0x2026),
}),
"x_mac_simp_chinese_ttx": ("gb2312", {
- b"\x80": unichr(0x00FC),
- b"\xA0": unichr(0x00A0),
- b"\xFD": unichr(0x00A9),
- b"\xFE": unichr(0x2122),
- b"\xFF": unichr(0x2026),
+ b"\x80": chr(0x00FC),
+ b"\xA0": chr(0x00A0),
+ b"\xFD": chr(0x00A9),
+ b"\xFE": chr(0x2122),
+ b"\xFF": chr(0x2026),
}),
}
diff --git a/Lib/fontTools/feaLib/__main__.py b/Lib/fontTools/feaLib/__main__.py
index da4eff6c..99c64231 100644
--- a/Lib/fontTools/feaLib/__main__.py
+++ b/Lib/fontTools/feaLib/__main__.py
@@ -1,7 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.feaLib.builder import addOpenTypeFeatures, Builder
+from fontTools.feaLib.error import FeatureLibError
from fontTools import configLogger
from fontTools.misc.cliTools import makeOutputFileName
import sys
@@ -13,21 +12,47 @@ log = logging.getLogger("fontTools.feaLib")
def main(args=None):
+ """Add features from a feature file (.fea) into a OTF font"""
parser = argparse.ArgumentParser(
- description="Use fontTools to compile OpenType feature files (*.fea).")
+ description="Use fontTools to compile OpenType feature files (*.fea)."
+ )
parser.add_argument(
- "input_fea", metavar="FEATURES", help="Path to the feature file")
+ "input_fea", metavar="FEATURES", help="Path to the feature file"
+ )
parser.add_argument(
- "input_font", metavar="INPUT_FONT", help="Path to the input font")
+ "input_font", metavar="INPUT_FONT", help="Path to the input font"
+ )
parser.add_argument(
- "-o", "--output", dest="output_font", metavar="OUTPUT_FONT",
- help="Path to the output font.")
+ "-o",
+ "--output",
+ dest="output_font",
+ metavar="OUTPUT_FONT",
+ help="Path to the output font.",
+ )
parser.add_argument(
- "-t", "--tables", metavar="TABLE_TAG", choices=Builder.supportedTables,
- nargs='+', help="Specify the table(s) to be built.")
+ "-t",
+ "--tables",
+ metavar="TABLE_TAG",
+ choices=Builder.supportedTables,
+ nargs="+",
+ help="Specify the table(s) to be built.",
+ )
parser.add_argument(
- "-v", "--verbose", help="increase the logger verbosity. Multiple -v "
- "options are allowed.", action="count", default=0)
+ "-d",
+ "--debug",
+ action="store_true",
+ help="Add source-level debugging information to font.",
+ )
+ parser.add_argument(
+ "-v",
+ "--verbose",
+ help="increase the logger verbosity. Multiple -v " "options are allowed.",
+ action="count",
+ default=0,
+ )
+ parser.add_argument(
+ "--traceback", help="show traceback for exceptions.", action="store_true"
+ )
options = parser.parse_args(args)
levels = ["WARNING", "INFO", "DEBUG"]
@@ -37,9 +62,16 @@ def main(args=None):
log.info("Compiling features to '%s'" % (output_font))
font = TTFont(options.input_font)
- addOpenTypeFeatures(font, options.input_fea, tables=options.tables)
+ try:
+ addOpenTypeFeatures(
+ font, options.input_fea, tables=options.tables, debug=options.debug
+ )
+ except FeatureLibError as e:
+ if options.traceback:
+ raise
+ log.error(e)
font.save(output_font)
-if __name__ == '__main__':
+if __name__ == "__main__":
sys.exit(main())
diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py
index 1994fc08..763d0d2c 100644
--- a/Lib/fontTools/feaLib/ast.py
+++ b/Lib/fontTools/feaLib/ast.py
@@ -1,7 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import byteord, tobytes
from fontTools.feaLib.error import FeatureLibError
+from fontTools.feaLib.location import FeatureLibLocation
from fontTools.misc.encodingTools import getEncoding
from collections import OrderedDict
import itertools
@@ -9,73 +8,71 @@ import itertools
SHIFT = " " * 4
__all__ = [
- 'AlternateSubstStatement',
- 'Anchor',
- 'AnchorDefinition',
- 'AnonymousBlock',
- 'AttachStatement',
- 'BaseAxis',
- 'Block',
- 'BytesIO',
- 'CVParametersNameStatement',
- 'ChainContextPosStatement',
- 'ChainContextSubstStatement',
- 'CharacterStatement',
- 'Comment',
- 'CursivePosStatement',
- 'Element',
- 'Expression',
- 'FeatureBlock',
- 'FeatureFile',
- 'FeatureLibError',
- 'FeatureNameStatement',
- 'FeatureReferenceStatement',
- 'FontRevisionStatement',
- 'GlyphClass',
- 'GlyphClassDefStatement',
- 'GlyphClassDefinition',
- 'GlyphClassName',
- 'GlyphName',
- 'HheaField',
- 'IgnorePosStatement',
- 'IgnoreSubstStatement',
- 'IncludeStatement',
- 'LanguageStatement',
- 'LanguageSystemStatement',
- 'LigatureCaretByIndexStatement',
- 'LigatureCaretByPosStatement',
- 'LigatureSubstStatement',
- 'LookupBlock',
- 'LookupFlagStatement',
- 'LookupReferenceStatement',
- 'MarkBasePosStatement',
- 'MarkClass',
- 'MarkClassDefinition',
- 'MarkClassName',
- 'MarkLigPosStatement',
- 'MarkMarkPosStatement',
- 'MultipleSubstStatement',
- 'NameRecord',
- 'NestedBlock',
- 'OS2Field',
- 'OrderedDict',
- 'PairPosStatement',
- 'Py23Error',
- 'ReverseChainSingleSubstStatement',
- 'ScriptStatement',
- 'SimpleNamespace',
- 'SinglePosStatement',
- 'SingleSubstStatement',
- 'SizeParameters',
- 'Statement',
- 'StringIO',
- 'SubtableStatement',
- 'TableBlock',
- 'Tag',
- 'UnicodeIO',
- 'ValueRecord',
- 'ValueRecordDefinition',
- 'VheaField',
+ "Element",
+ "FeatureFile",
+ "Comment",
+ "GlyphName",
+ "GlyphClass",
+ "GlyphClassName",
+ "MarkClassName",
+ "AnonymousBlock",
+ "Block",
+ "FeatureBlock",
+ "NestedBlock",
+ "LookupBlock",
+ "GlyphClassDefinition",
+ "GlyphClassDefStatement",
+ "MarkClass",
+ "MarkClassDefinition",
+ "AlternateSubstStatement",
+ "Anchor",
+ "AnchorDefinition",
+ "AttachStatement",
+ "AxisValueLocationStatement",
+ "BaseAxis",
+ "CVParametersNameStatement",
+ "ChainContextPosStatement",
+ "ChainContextSubstStatement",
+ "CharacterStatement",
+ "CursivePosStatement",
+ "ElidedFallbackName",
+ "ElidedFallbackNameID",
+ "Expression",
+ "FeatureNameStatement",
+ "FeatureReferenceStatement",
+ "FontRevisionStatement",
+ "HheaField",
+ "IgnorePosStatement",
+ "IgnoreSubstStatement",
+ "IncludeStatement",
+ "LanguageStatement",
+ "LanguageSystemStatement",
+ "LigatureCaretByIndexStatement",
+ "LigatureCaretByPosStatement",
+ "LigatureSubstStatement",
+ "LookupFlagStatement",
+ "LookupReferenceStatement",
+ "MarkBasePosStatement",
+ "MarkLigPosStatement",
+ "MarkMarkPosStatement",
+ "MultipleSubstStatement",
+ "NameRecord",
+ "OS2Field",
+ "PairPosStatement",
+ "ReverseChainSingleSubstStatement",
+ "ScriptStatement",
+ "SinglePosStatement",
+ "SingleSubstStatement",
+ "SizeParameters",
+ "Statement",
+ "STATAxisValueStatement",
+ "STATDesignAxisStatement",
+ "STATNameStatement",
+ "SubtableStatement",
+ "TableBlock",
+ "ValueRecord",
+ "ValueRecordDefinition",
+ "VheaField",
]
@@ -86,32 +83,69 @@ def deviceToString(device):
return "<device %s>" % ", ".join("%d %d" % t for t in device)
-fea_keywords = set([
- "anchor", "anchordef", "anon", "anonymous",
- "by",
- "contour", "cursive",
- "device",
- "enum", "enumerate", "excludedflt", "exclude_dflt",
- "feature", "from",
- "ignore", "ignorebaseglyphs", "ignoreligatures", "ignoremarks",
- "include", "includedflt", "include_dflt",
- "language", "languagesystem", "lookup", "lookupflag",
- "mark", "markattachmenttype", "markclass",
- "nameid", "null",
- "parameters", "pos", "position",
- "required", "righttoleft", "reversesub", "rsub",
- "script", "sub", "substitute", "subtable",
- "table",
- "usemarkfilteringset", "useextension", "valuerecorddef",
- "base", "gdef", "head", "hhea", "name", "vhea", "vmtx"]
+fea_keywords = set(
+ [
+ "anchor",
+ "anchordef",
+ "anon",
+ "anonymous",
+ "by",
+ "contour",
+ "cursive",
+ "device",
+ "enum",
+ "enumerate",
+ "excludedflt",
+ "exclude_dflt",
+ "feature",
+ "from",
+ "ignore",
+ "ignorebaseglyphs",
+ "ignoreligatures",
+ "ignoremarks",
+ "include",
+ "includedflt",
+ "include_dflt",
+ "language",
+ "languagesystem",
+ "lookup",
+ "lookupflag",
+ "mark",
+ "markattachmenttype",
+ "markclass",
+ "nameid",
+ "null",
+ "parameters",
+ "pos",
+ "position",
+ "required",
+ "righttoleft",
+ "reversesub",
+ "rsub",
+ "script",
+ "sub",
+ "substitute",
+ "subtable",
+ "table",
+ "usemarkfilteringset",
+ "useextension",
+ "valuerecorddef",
+ "base",
+ "gdef",
+ "head",
+ "hhea",
+ "name",
+ "vhea",
+ "vmtx",
+ ]
)
def asFea(g):
- if hasattr(g, 'asFea'):
+ if hasattr(g, "asFea"):
return g.asFea()
elif isinstance(g, tuple) and len(g) == 2:
- return asFea(g[0]) + "-" + asFea(g[1]) # a range
+ return asFea(g[0]) + " - " + asFea(g[1]) # a range
elif g.lower() in fea_keywords:
return "\\" + g
else:
@@ -119,14 +153,21 @@ def asFea(g):
class Element(object):
+ """A base class representing "something" in a feature file."""
def __init__(self, location=None):
+ #: location of this element as a `FeatureLibLocation` object.
+ if location and not isinstance(location, FeatureLibLocation):
+ location = FeatureLibLocation(*location)
self.location = location
def build(self, builder):
pass
def asFea(self, indent=""):
+ """Returns this element as a string of feature code. For block-type
+ elements (such as :class:`FeatureBlock`), the `indent` string is
+ added to the start of each line in the output."""
raise NotImplementedError
def __str__(self):
@@ -142,21 +183,42 @@ class Expression(Element):
class Comment(Element):
+ """A comment in a feature file."""
+
def __init__(self, text, location=None):
super(Comment, self).__init__(location)
+ #: Text of the comment
self.text = text
def asFea(self, indent=""):
return self.text
+class NullGlyph(Expression):
+ """The NULL glyph, used in glyph deletion substitutions."""
+
+ def __init__(self, location=None):
+ Expression.__init__(self, location)
+ #: The name itself as a string
+
+ def glyphSet(self):
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
+ return ()
+
+ def asFea(self, indent=""):
+ return "NULL"
+
+
class GlyphName(Expression):
- """A single glyph name, such as cedilla."""
+ """A single glyph name, such as ``cedilla``."""
+
def __init__(self, glyph, location=None):
Expression.__init__(self, location)
+ #: The name itself as a string
self.glyph = glyph
def glyphSet(self):
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
return (self.glyph,)
def asFea(self, indent=""):
@@ -164,61 +226,78 @@ class GlyphName(Expression):
class GlyphClass(Expression):
- """A glyph class, such as [acute cedilla grave]."""
+ """A glyph class, such as ``[acute cedilla grave]``."""
+
def __init__(self, glyphs=None, location=None):
Expression.__init__(self, location)
+ #: The list of glyphs in this class, as :class:`GlyphName` objects.
self.glyphs = glyphs if glyphs is not None else []
self.original = []
self.curr = 0
def glyphSet(self):
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
return tuple(self.glyphs)
def asFea(self, indent=""):
if len(self.original):
if self.curr < len(self.glyphs):
- self.original.extend(self.glyphs[self.curr:])
+ self.original.extend(self.glyphs[self.curr :])
self.curr = len(self.glyphs)
return "[" + " ".join(map(asFea, self.original)) + "]"
else:
return "[" + " ".join(map(asFea, self.glyphs)) + "]"
def extend(self, glyphs):
+ """Add a list of :class:`GlyphName` objects to the class."""
self.glyphs.extend(glyphs)
def append(self, glyph):
+ """Add a single :class:`GlyphName` object to the class."""
self.glyphs.append(glyph)
def add_range(self, start, end, glyphs):
+ """Add a range (e.g. ``A-Z``) to the class. ``start`` and ``end``
+ are either :class:`GlyphName` objects or strings representing the
+ start and end glyphs in the class, and ``glyphs`` is the full list of
+ :class:`GlyphName` objects in the range."""
if self.curr < len(self.glyphs):
- self.original.extend(self.glyphs[self.curr:])
+ self.original.extend(self.glyphs[self.curr :])
self.original.append((start, end))
self.glyphs.extend(glyphs)
self.curr = len(self.glyphs)
def add_cid_range(self, start, end, glyphs):
+ """Add a range to the class by glyph ID. ``start`` and ``end`` are the
+ initial and final IDs, and ``glyphs`` is the full list of
+ :class:`GlyphName` objects in the range."""
if self.curr < len(self.glyphs):
- self.original.extend(self.glyphs[self.curr:])
- self.original.append(("cid{:05d}".format(start), "cid{:05d}".format(end)))
+ self.original.extend(self.glyphs[self.curr :])
+ self.original.append(("\\{}".format(start), "\\{}".format(end)))
self.glyphs.extend(glyphs)
self.curr = len(self.glyphs)
def add_class(self, gc):
+ """Add glyphs from the given :class:`GlyphClassName` object to the
+ class."""
if self.curr < len(self.glyphs):
- self.original.extend(self.glyphs[self.curr:])
+ self.original.extend(self.glyphs[self.curr :])
self.original.append(gc)
self.glyphs.extend(gc.glyphSet())
self.curr = len(self.glyphs)
class GlyphClassName(Expression):
- """A glyph class name, such as @FRENCH_MARKS."""
+ """A glyph class name, such as ``@FRENCH_MARKS``. This must be instantiated
+ with a :class:`GlyphClassDefinition` object."""
+
def __init__(self, glyphclass, location=None):
Expression.__init__(self, location)
assert isinstance(glyphclass, GlyphClassDefinition)
self.glyphclass = glyphclass
def glyphSet(self):
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
return tuple(self.glyphclass.glyphSet())
def asFea(self, indent=""):
@@ -226,13 +305,16 @@ class GlyphClassName(Expression):
class MarkClassName(Expression):
- """A mark class name, such as @FRENCH_MARKS defined with markClass."""
+ """A mark class name, such as ``@FRENCH_MARKS`` defined with ``markClass``.
+ This must be instantiated with a :class:`MarkClass` object."""
+
def __init__(self, markClass, location=None):
Expression.__init__(self, location)
assert isinstance(markClass, MarkClass)
self.markClass = markClass
def glyphSet(self):
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
return self.markClass.glyphSet()
def asFea(self, indent=""):
@@ -240,9 +322,12 @@ class MarkClassName(Expression):
class AnonymousBlock(Statement):
+ """An anonymous data block."""
+
def __init__(self, tag, content, location=None):
Statement.__init__(self, location)
- self.tag, self.content = tag, content
+ self.tag = tag #: string containing the block's "tag"
+ self.content = content #: block data as string
def asFea(self, indent=""):
res = "anon {} {{\n".format(self.tag)
@@ -252,21 +337,32 @@ class AnonymousBlock(Statement):
class Block(Statement):
+ """A block of statements: feature, lookup, etc."""
+
def __init__(self, location=None):
Statement.__init__(self, location)
- self.statements = []
+ self.statements = [] #: Statements contained in the block
def build(self, builder):
+ """When handed a 'builder' object of comparable interface to
+ :class:`fontTools.feaLib.builder`, walks the statements in this
+ block, calling the builder callbacks."""
for s in self.statements:
s.build(builder)
def asFea(self, indent=""):
indent += SHIFT
- return indent + ("\n" + indent).join(
- [s.asFea(indent=indent) for s in self.statements]) + "\n"
+ return (
+ indent
+ + ("\n" + indent).join([s.asFea(indent=indent) for s in self.statements])
+ + "\n"
+ )
class FeatureFile(Block):
+ """The top-level element of the syntax tree, containing the whole feature
+ file in its ``statements`` attribute."""
+
def __init__(self):
Block.__init__(self, location=None)
self.markClasses = {} # name --> ast.MarkClass
@@ -276,11 +372,15 @@ class FeatureFile(Block):
class FeatureBlock(Block):
+ """A named feature block."""
+
def __init__(self, name, use_extension=False, location=None):
Block.__init__(self, location)
self.name, self.use_extension = name, use_extension
def build(self, builder):
+ """Call the ``start_feature`` callback on the builder object, visit
+ all the statements in this feature, and then call ``end_feature``."""
# TODO(sascha): Handle use_extension.
builder.start_feature(self.location, self.name)
# language exclude_dflt statements modify builder.features_
@@ -304,6 +404,9 @@ class FeatureBlock(Block):
class NestedBlock(Block):
+ """A block inside another block, for example when found inside a
+ ``cvParameters`` block."""
+
def __init__(self, tag, block_name, location=None):
Block.__init__(self, location)
self.tag = tag
@@ -322,6 +425,8 @@ class NestedBlock(Block):
class LookupBlock(Block):
+ """A named lookup, containing ``statements``."""
+
def __init__(self, name, use_extension=False, location=None):
Block.__init__(self, location)
self.name, self.use_extension = name, use_extension
@@ -343,6 +448,8 @@ class LookupBlock(Block):
class TableBlock(Block):
+ """A ``table ... { }`` block."""
+
def __init__(self, name, location=None):
Block.__init__(self, location)
self.name = name
@@ -355,13 +462,15 @@ class TableBlock(Block):
class GlyphClassDefinition(Statement):
- """Example: @UPPERCASE = [A-Z];"""
+ """Example: ``@UPPERCASE = [A-Z];``."""
+
def __init__(self, name, glyphs, location=None):
Statement.__init__(self, location)
- self.name = name
- self.glyphs = glyphs
+ self.name = name #: class name as a string, without initial ``@``
+ self.glyphs = glyphs #: a :class:`GlyphClass` object
def glyphSet(self):
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
return tuple(self.glyphs.glyphSet())
def asFea(self, indent=""):
@@ -369,21 +478,24 @@ class GlyphClassDefinition(Statement):
class GlyphClassDefStatement(Statement):
- """Example: GlyphClassDef @UPPERCASE, [B], [C], [D];"""
- def __init__(self, baseGlyphs, markGlyphs, ligatureGlyphs,
- componentGlyphs, location=None):
+ """Example: ``GlyphClassDef @UPPERCASE, [B], [C], [D];``. The parameters
+ must be either :class:`GlyphClass` or :class:`GlyphClassName` objects, or
+ ``None``."""
+
+ def __init__(
+ self, baseGlyphs, markGlyphs, ligatureGlyphs, componentGlyphs, location=None
+ ):
Statement.__init__(self, location)
self.baseGlyphs, self.markGlyphs = (baseGlyphs, markGlyphs)
self.ligatureGlyphs = ligatureGlyphs
self.componentGlyphs = componentGlyphs
def build(self, builder):
+ """Calls the builder's ``add_glyphClassDef`` callback."""
base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple()
- liga = self.ligatureGlyphs.glyphSet() \
- if self.ligatureGlyphs else tuple()
+ liga = self.ligatureGlyphs.glyphSet() if self.ligatureGlyphs else tuple()
mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple()
- comp = (self.componentGlyphs.glyphSet()
- if self.componentGlyphs else tuple())
+ comp = self.componentGlyphs.glyphSet() if self.componentGlyphs else tuple()
builder.add_glyphClassDef(self.location, base, liga, mark, comp)
def asFea(self, indent=""):
@@ -391,22 +503,32 @@ class GlyphClassDefStatement(Statement):
self.baseGlyphs.asFea() if self.baseGlyphs else "",
self.ligatureGlyphs.asFea() if self.ligatureGlyphs else "",
self.markGlyphs.asFea() if self.markGlyphs else "",
- self.componentGlyphs.asFea() if self.componentGlyphs else "")
+ self.componentGlyphs.asFea() if self.componentGlyphs else "",
+ )
-# While glyph classes can be defined only once, the feature file format
-# allows expanding mark classes with multiple definitions, each using
-# different glyphs and anchors. The following are two MarkClassDefinitions
-# for the same MarkClass:
-# markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
-# markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
class MarkClass(object):
+ """One `or more` ``markClass`` statements for the same mark class.
+
+ While glyph classes can be defined only once, the feature file format
+ allows expanding mark classes with multiple definitions, each using
+ different glyphs and anchors. The following are two ``MarkClassDefinitions``
+ for the same ``MarkClass``::
+
+ markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
+ markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
+
+ The ``MarkClass`` object is therefore just a container for a list of
+ :class:`MarkClassDefinition` statements.
+ """
+
def __init__(self, name):
self.name = name
self.definitions = []
self.glyphs = OrderedDict() # glyph --> ast.MarkClassDefinitions
def addDefinition(self, definition):
+ """Add a :class:`MarkClassDefinition` statement to this mark class."""
assert isinstance(definition, MarkClassDefinition)
self.definitions.append(definition)
for glyph in definition.glyphSet():
@@ -415,14 +537,14 @@ class MarkClass(object):
if otherLoc is None:
end = ""
else:
- end = " at %s:%d:%d" % (
- otherLoc[0], otherLoc[1], otherLoc[2])
+ end = f" at {otherLoc}"
raise FeatureLibError(
- "Glyph %s already defined%s" % (glyph, end),
- definition.location)
+ "Glyph %s already defined%s" % (glyph, end), definition.location
+ )
self.glyphs[glyph] = definition
def glyphSet(self):
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
return tuple(self.glyphs.keys())
def asFea(self, indent=""):
@@ -431,6 +553,28 @@ class MarkClass(object):
class MarkClassDefinition(Statement):
+ """A single ``markClass`` statement. The ``markClass`` should be a
+ :class:`MarkClass` object, the ``anchor`` an :class:`Anchor` object,
+ and the ``glyphs`` parameter should be a `glyph-containing object`_ .
+
+ Example:
+
+ .. code:: python
+
+ mc = MarkClass("FRENCH_ACCENTS")
+ mc.addDefinition( MarkClassDefinition(mc, Anchor(350, 800),
+ GlyphClass([ GlyphName("acute"), GlyphName("grave") ])
+ ) )
+ mc.addDefinition( MarkClassDefinition(mc, Anchor(350, -200),
+ GlyphClass([ GlyphName("cedilla") ])
+ ) )
+
+ mc.asFea()
+ # markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
+ # markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
+
+ """
+
def __init__(self, markClass, anchor, glyphs, location=None):
Statement.__init__(self, location)
assert isinstance(markClass, MarkClass)
@@ -438,36 +582,42 @@ class MarkClassDefinition(Statement):
self.markClass, self.anchor, self.glyphs = markClass, anchor, glyphs
def glyphSet(self):
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
return self.glyphs.glyphSet()
def asFea(self, indent=""):
return "markClass {} {} @{};".format(
- self.glyphs.asFea(), self.anchor.asFea(),
- self.markClass.name)
+ self.glyphs.asFea(), self.anchor.asFea(), self.markClass.name
+ )
class AlternateSubstStatement(Statement):
+ """A ``sub ... from ...`` statement.
+
+ ``prefix``, ``glyph``, ``suffix`` and ``replacement`` should be lists of
+ `glyph-containing objects`_. ``glyph`` should be a `one element list`."""
+
def __init__(self, prefix, glyph, suffix, replacement, location=None):
Statement.__init__(self, location)
self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix)
self.replacement = replacement
def build(self, builder):
+ """Calls the builder's ``add_alternate_subst`` callback."""
glyph = self.glyph.glyphSet()
assert len(glyph) == 1, glyph
glyph = list(glyph)[0]
prefix = [p.glyphSet() for p in self.prefix]
suffix = [s.glyphSet() for s in self.suffix]
replacement = self.replacement.glyphSet()
- builder.add_alternate_subst(self.location, prefix, glyph, suffix,
- replacement)
+ builder.add_alternate_subst(self.location, prefix, glyph, suffix, replacement)
def asFea(self, indent=""):
res = "sub "
if len(self.prefix) or len(self.suffix):
if len(self.prefix):
res += " ".join(map(asFea, self.prefix)) + " "
- res += asFea(self.glyph) + "'" # even though we really only use 1
+ res += asFea(self.glyph) + "'" # even though we really only use 1
if len(self.suffix):
res += " " + " ".join(map(asFea, self.suffix))
else:
@@ -479,8 +629,22 @@ class AlternateSubstStatement(Statement):
class Anchor(Expression):
- def __init__(self, x, y, name=None, contourpoint=None,
- xDeviceTable=None, yDeviceTable=None, location=None):
+ """An ``Anchor`` element, used inside a ``pos`` rule.
+
+ If a ``name`` is given, this will be used in preference to the coordinates.
+ Other values should be integer.
+ """
+
+ def __init__(
+ self,
+ x,
+ y,
+ name=None,
+ contourpoint=None,
+ xDeviceTable=None,
+ yDeviceTable=None,
+ location=None,
+ ):
Expression.__init__(self, location)
self.name = name
self.x, self.y, self.contourpoint = x, y, contourpoint
@@ -502,6 +666,8 @@ class Anchor(Expression):
class AnchorDefinition(Statement):
+ """A named anchor definition. (2.e.viii). ``name`` should be a string."""
+
def __init__(self, name, x, y, contourpoint=None, location=None):
Statement.__init__(self, location)
self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint
@@ -515,41 +681,72 @@ class AnchorDefinition(Statement):
class AttachStatement(Statement):
+ """A ``GDEF`` table ``Attach`` statement."""
+
def __init__(self, glyphs, contourPoints, location=None):
Statement.__init__(self, location)
- self.glyphs, self.contourPoints = (glyphs, contourPoints)
+ self.glyphs = glyphs #: A `glyph-containing object`_
+ self.contourPoints = contourPoints #: A list of integer contour points
def build(self, builder):
+ """Calls the builder's ``add_attach_points`` callback."""
glyphs = self.glyphs.glyphSet()
builder.add_attach_points(self.location, glyphs, self.contourPoints)
def asFea(self, indent=""):
return "Attach {} {};".format(
- self.glyphs.asFea(), " ".join(str(c) for c in self.contourPoints))
+ self.glyphs.asFea(), " ".join(str(c) for c in self.contourPoints)
+ )
class ChainContextPosStatement(Statement):
+ """A chained contextual positioning statement.
+
+ ``prefix``, ``glyphs``, and ``suffix`` should be lists of
+ `glyph-containing objects`_ .
+
+ ``lookups`` should be a list of elements representing what lookups
+ to apply at each glyph position. Each element should be a
+ :class:`LookupBlock` to apply a single chaining lookup at the given
+ position, a list of :class:`LookupBlock`\ s to apply multiple
+ lookups, or ``None`` to apply no lookup. The length of the outer
+ list should equal the length of ``glyphs``; the inner lists can be
+ of variable length."""
+
def __init__(self, prefix, glyphs, suffix, lookups, location=None):
Statement.__init__(self, location)
self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
- self.lookups = lookups
+ self.lookups = list(lookups)
+ for i, lookup in enumerate(lookups):
+ if lookup:
+ try:
+ (_ for _ in lookup)
+ except TypeError:
+ self.lookups[i] = [lookup]
def build(self, builder):
+ """Calls the builder's ``add_chain_context_pos`` callback."""
prefix = [p.glyphSet() for p in self.prefix]
glyphs = [g.glyphSet() for g in self.glyphs]
suffix = [s.glyphSet() for s in self.suffix]
builder.add_chain_context_pos(
- self.location, prefix, glyphs, suffix, self.lookups)
+ self.location, prefix, glyphs, suffix, self.lookups
+ )
def asFea(self, indent=""):
res = "pos "
- if len(self.prefix) or len(self.suffix) or any([x is not None for x in self.lookups]):
+ if (
+ len(self.prefix)
+ or len(self.suffix)
+ or any([x is not None for x in self.lookups])
+ ):
if len(self.prefix):
res += " ".join(g.asFea() for g in self.prefix) + " "
for i, g in enumerate(self.glyphs):
res += g.asFea() + "'"
- if self.lookups[i] is not None:
- res += " lookup " + self.lookups[i].name
+ if self.lookups[i]:
+ for lu in self.lookups[i]:
+ res += " lookup " + lu.name
if i < len(self.glyphs) - 1:
res += " "
if len(self.suffix):
@@ -561,27 +758,53 @@ class ChainContextPosStatement(Statement):
class ChainContextSubstStatement(Statement):
+ """A chained contextual substitution statement.
+
+ ``prefix``, ``glyphs``, and ``suffix`` should be lists of
+ `glyph-containing objects`_ .
+
+ ``lookups`` should be a list of elements representing what lookups
+ to apply at each glyph position. Each element should be a
+ :class:`LookupBlock` to apply a single chaining lookup at the given
+ position, a list of :class:`LookupBlock`\ s to apply multiple
+ lookups, or ``None`` to apply no lookup. The length of the outer
+ list should equal the length of ``glyphs``; the inner lists can be
+ of variable length."""
+
def __init__(self, prefix, glyphs, suffix, lookups, location=None):
Statement.__init__(self, location)
self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
- self.lookups = lookups
+ self.lookups = list(lookups)
+ for i, lookup in enumerate(lookups):
+ if lookup:
+ try:
+ (_ for _ in lookup)
+ except TypeError:
+ self.lookups[i] = [lookup]
def build(self, builder):
+ """Calls the builder's ``add_chain_context_subst`` callback."""
prefix = [p.glyphSet() for p in self.prefix]
glyphs = [g.glyphSet() for g in self.glyphs]
suffix = [s.glyphSet() for s in self.suffix]
builder.add_chain_context_subst(
- self.location, prefix, glyphs, suffix, self.lookups)
+ self.location, prefix, glyphs, suffix, self.lookups
+ )
def asFea(self, indent=""):
res = "sub "
- if len(self.prefix) or len(self.suffix) or any([x is not None for x in self.lookups]):
+ if (
+ len(self.prefix)
+ or len(self.suffix)
+ or any([x is not None for x in self.lookups])
+ ):
if len(self.prefix):
res += " ".join(g.asFea() for g in self.prefix) + " "
for i, g in enumerate(self.glyphs):
res += g.asFea() + "'"
- if self.lookups[i] is not None:
- res += " lookup " + self.lookups[i].name
+ if self.lookups[i]:
+ for lu in self.lookups[i]:
+ res += " lookup " + lu.name
if i < len(self.glyphs) - 1:
res += " "
if len(self.suffix):
@@ -593,14 +816,19 @@ class ChainContextSubstStatement(Statement):
class CursivePosStatement(Statement):
+ """A cursive positioning statement. Entry and exit anchors can either
+ be :class:`Anchor` objects or ``None``."""
+
def __init__(self, glyphclass, entryAnchor, exitAnchor, location=None):
Statement.__init__(self, location)
self.glyphclass = glyphclass
self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor
def build(self, builder):
+ """Calls the builder object's ``add_cursive_pos`` callback."""
builder.add_cursive_pos(
- self.location, self.glyphclass.glyphSet(), self.entryAnchor, self.exitAnchor)
+ self.location, self.glyphclass.glyphSet(), self.entryAnchor, self.exitAnchor
+ )
def asFea(self, indent=""):
entry = self.entryAnchor.asFea() if self.entryAnchor else "<anchor NULL>"
@@ -609,12 +837,14 @@ class CursivePosStatement(Statement):
class FeatureReferenceStatement(Statement):
- """Example: feature salt;"""
+ """Example: ``feature salt;``"""
+
def __init__(self, featureName, location=None):
Statement.__init__(self, location)
self.location, self.featureName = (location, featureName)
def build(self, builder):
+ """Calls the builder object's ``add_feature_reference`` callback."""
builder.add_feature_reference(self.location, self.featureName)
def asFea(self, indent=""):
@@ -622,17 +852,24 @@ class FeatureReferenceStatement(Statement):
class IgnorePosStatement(Statement):
+ """An ``ignore pos`` statement, containing `one or more` contexts to ignore.
+
+ ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
+ with each of ``prefix``, ``glyphs`` and ``suffix`` being
+ `glyph-containing objects`_ ."""
+
def __init__(self, chainContexts, location=None):
Statement.__init__(self, location)
self.chainContexts = chainContexts
def build(self, builder):
+ """Calls the builder object's ``add_chain_context_pos`` callback on each
+ rule context."""
for prefix, glyphs, suffix in self.chainContexts:
prefix = [p.glyphSet() for p in prefix]
glyphs = [g.glyphSet() for g in glyphs]
suffix = [s.glyphSet() for s in suffix]
- builder.add_chain_context_pos(
- self.location, prefix, glyphs, suffix, [])
+ builder.add_chain_context_pos(self.location, prefix, glyphs, suffix, [])
def asFea(self, indent=""):
contexts = []
@@ -651,17 +888,24 @@ class IgnorePosStatement(Statement):
class IgnoreSubstStatement(Statement):
+ """An ``ignore sub`` statement, containing `one or more` contexts to ignore.
+
+ ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
+ with each of ``prefix``, ``glyphs`` and ``suffix`` being
+ `glyph-containing objects`_ ."""
+
def __init__(self, chainContexts, location=None):
Statement.__init__(self, location)
self.chainContexts = chainContexts
def build(self, builder):
+ """Calls the builder object's ``add_chain_context_subst`` callback on
+ each rule context."""
for prefix, glyphs, suffix in self.chainContexts:
prefix = [p.glyphSet() for p in prefix]
glyphs = [g.glyphSet() for g in glyphs]
suffix = [s.glyphSet() for s in suffix]
- builder.add_chain_context_subst(
- self.location, prefix, glyphs, suffix, [])
+ builder.add_chain_context_subst(self.location, prefix, glyphs, suffix, [])
def asFea(self, indent=""):
contexts = []
@@ -680,34 +924,42 @@ class IgnoreSubstStatement(Statement):
class IncludeStatement(Statement):
+ """An ``include()`` statement."""
+
def __init__(self, filename, location=None):
super(IncludeStatement, self).__init__(location)
- self.filename = filename
+ self.filename = filename #: String containing name of file to include
def build(self):
# TODO: consider lazy-loading the including parser/lexer?
raise FeatureLibError(
"Building an include statement is not implemented yet. "
"Instead, use Parser(..., followIncludes=True) for building.",
- self.location)
+ self.location,
+ )
def asFea(self, indent=""):
return indent + "include(%s);" % self.filename
class LanguageStatement(Statement):
- def __init__(self, language, include_default=True, required=False,
- location=None):
+ """A ``language`` statement within a feature."""
+
+ def __init__(self, language, include_default=True, required=False, location=None):
Statement.__init__(self, location)
- assert(len(language) == 4)
- self.language = language
- self.include_default = include_default
+ assert len(language) == 4
+ self.language = language #: A four-character language tag
+ self.include_default = include_default #: If false, "exclude_dflt"
self.required = required
def build(self, builder):
- builder.set_language(location=self.location, language=self.language,
- include_default=self.include_default,
- required=self.required)
+ """Call the builder object's ``set_language`` callback."""
+ builder.set_language(
+ location=self.location,
+ language=self.language,
+ include_default=self.include_default,
+ required=self.required,
+ )
def asFea(self, indent=""):
res = "language {}".format(self.language.strip())
@@ -720,11 +972,14 @@ class LanguageStatement(Statement):
class LanguageSystemStatement(Statement):
+ """A top-level ``languagesystem`` statement."""
+
def __init__(self, script, language, location=None):
Statement.__init__(self, location)
self.script, self.language = (script, language)
def build(self, builder):
+ """Calls the builder object's ``add_language_system`` callback."""
builder.add_language_system(self.location, self.script, self.language)
def asFea(self, indent=""):
@@ -732,6 +987,9 @@ class LanguageSystemStatement(Statement):
class FontRevisionStatement(Statement):
+ """A ``head`` table ``FontRevision`` statement. ``revision`` should be a
+ number, and will be formatted to three significant decimal places."""
+
def __init__(self, revision, location=None):
Statement.__init__(self, location)
self.revision = revision
@@ -744,36 +1002,54 @@ class FontRevisionStatement(Statement):
class LigatureCaretByIndexStatement(Statement):
+ """A ``GDEF`` table ``LigatureCaretByIndex`` statement. ``glyphs`` should be
+ a `glyph-containing object`_, and ``carets`` should be a list of integers."""
+
def __init__(self, glyphs, carets, location=None):
Statement.__init__(self, location)
self.glyphs, self.carets = (glyphs, carets)
def build(self, builder):
+ """Calls the builder object's ``add_ligatureCaretByIndex_`` callback."""
glyphs = self.glyphs.glyphSet()
builder.add_ligatureCaretByIndex_(self.location, glyphs, set(self.carets))
def asFea(self, indent=""):
return "LigatureCaretByIndex {} {};".format(
- self.glyphs.asFea(), " ".join(str(x) for x in self.carets))
+ self.glyphs.asFea(), " ".join(str(x) for x in self.carets)
+ )
class LigatureCaretByPosStatement(Statement):
+ """A ``GDEF`` table ``LigatureCaretByPos`` statement. ``glyphs`` should be
+ a `glyph-containing object`_, and ``carets`` should be a list of integers."""
+
def __init__(self, glyphs, carets, location=None):
Statement.__init__(self, location)
self.glyphs, self.carets = (glyphs, carets)
def build(self, builder):
+ """Calls the builder object's ``add_ligatureCaretByPos_`` callback."""
glyphs = self.glyphs.glyphSet()
builder.add_ligatureCaretByPos_(self.location, glyphs, set(self.carets))
def asFea(self, indent=""):
return "LigatureCaretByPos {} {};".format(
- self.glyphs.asFea(), " ".join(str(x) for x in self.carets))
+ self.glyphs.asFea(), " ".join(str(x) for x in self.carets)
+ )
class LigatureSubstStatement(Statement):
- def __init__(self, prefix, glyphs, suffix, replacement,
- forceChain, location=None):
+ """A chained contextual substitution statement.
+
+ ``prefix``, ``glyphs``, and ``suffix`` should be lists of
+ `glyph-containing objects`_; ``replacement`` should be a single
+ `glyph-containing object`_.
+
+ If ``forceChain`` is True, this is expressed as a chaining rule
+ (e.g. ``sub f' i' by f_i``) even when no context is given."""
+
+ def __init__(self, prefix, glyphs, suffix, replacement, forceChain, location=None):
Statement.__init__(self, location)
self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix)
self.replacement, self.forceChain = replacement, forceChain
@@ -783,8 +1059,8 @@ class LigatureSubstStatement(Statement):
glyphs = [g.glyphSet() for g in self.glyphs]
suffix = [s.glyphSet() for s in self.suffix]
builder.add_ligature_subst(
- self.location, prefix, glyphs, suffix, self.replacement,
- self.forceChain)
+ self.location, prefix, glyphs, suffix, self.replacement, self.forceChain
+ )
def asFea(self, indent=""):
res = "sub "
@@ -803,22 +1079,28 @@ class LigatureSubstStatement(Statement):
class LookupFlagStatement(Statement):
- def __init__(self, value=0, markAttachment=None, markFilteringSet=None,
- location=None):
+ """A ``lookupflag`` statement. The ``value`` should be an integer value
+ representing the flags in use, but not including the ``markAttachment``
+ class and ``markFilteringSet`` values, which must be specified as
+ glyph-containing objects."""
+
+ def __init__(
+ self, value=0, markAttachment=None, markFilteringSet=None, location=None
+ ):
Statement.__init__(self, location)
self.value = value
self.markAttachment = markAttachment
self.markFilteringSet = markFilteringSet
def build(self, builder):
+ """Calls the builder object's ``set_lookup_flag`` callback."""
markAttach = None
if self.markAttachment is not None:
markAttach = self.markAttachment.glyphSet()
markFilter = None
if self.markFilteringSet is not None:
markFilter = self.markFilteringSet.glyphSet()
- builder.set_lookup_flag(self.location, self.value,
- markAttach, markFilter)
+ builder.set_lookup_flag(self.location, self.value, markAttach, markFilter)
def asFea(self, indent=""):
res = []
@@ -838,11 +1120,16 @@ class LookupFlagStatement(Statement):
class LookupReferenceStatement(Statement):
+ """Represents a ``lookup ...;`` statement to include a lookup in a feature.
+
+ The ``lookup`` should be a :class:`LookupBlock` object."""
+
def __init__(self, lookup, location=None):
Statement.__init__(self, location)
self.location, self.lookup = (location, lookup)
def build(self, builder):
+ """Calls the builder object's ``add_lookup_call`` callback."""
builder.add_lookup_call(self.lookup.name)
def asFea(self, indent=""):
@@ -850,27 +1137,59 @@ class LookupReferenceStatement(Statement):
class MarkBasePosStatement(Statement):
+ """A mark-to-base positioning rule. The ``base`` should be a
+ `glyph-containing object`_. The ``marks`` should be a list of
+ (:class:`Anchor`, :class:`MarkClass`) tuples."""
+
def __init__(self, base, marks, location=None):
Statement.__init__(self, location)
self.base, self.marks = base, marks
def build(self, builder):
+ """Calls the builder object's ``add_mark_base_pos`` callback."""
builder.add_mark_base_pos(self.location, self.base.glyphSet(), self.marks)
def asFea(self, indent=""):
res = "pos base {}".format(self.base.asFea())
for a, m in self.marks:
- res += " {} mark @{}".format(a.asFea(), m.name)
+ res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
res += ";"
return res
class MarkLigPosStatement(Statement):
+ """A mark-to-ligature positioning rule. The ``ligatures`` must be a
+ `glyph-containing object`_. The ``marks`` should be a list of lists: each
+ element in the top-level list represents a component glyph, and is made
+ up of a list of (:class:`Anchor`, :class:`MarkClass`) tuples representing
+ mark attachment points for that position.
+
+ Example::
+
+ m1 = MarkClass("TOP_MARKS")
+ m2 = MarkClass("BOTTOM_MARKS")
+ # ... add definitions to mark classes...
+
+ glyph = GlyphName("lam_meem_jeem")
+ marks = [
+ [ (Anchor(625,1800), m1) ], # Attachments on 1st component (lam)
+ [ (Anchor(376,-378), m2) ], # Attachments on 2nd component (meem)
+ [ ] # No attachments on the jeem
+ ]
+ mlp = MarkLigPosStatement(glyph, marks)
+
+ mlp.asFea()
+ # pos ligature lam_meem_jeem <anchor 625 1800> mark @TOP_MARKS
+ # ligComponent <anchor 376 -378> mark @BOTTOM_MARKS;
+
+ """
+
def __init__(self, ligatures, marks, location=None):
Statement.__init__(self, location)
self.ligatures, self.marks = ligatures, marks
def build(self, builder):
+ """Calls the builder object's ``add_mark_lig_pos`` callback."""
builder.add_mark_lig_pos(self.location, self.ligatures.glyphSet(), self.marks)
def asFea(self, indent=""):
@@ -879,10 +1198,15 @@ class MarkLigPosStatement(Statement):
for l in self.marks:
temp = ""
if l is None or not len(l):
- temp = " <anchor NULL>"
+ temp = "\n" + indent + SHIFT * 2 + "<anchor NULL>"
else:
for a, m in l:
- temp += " {} mark @{}".format(a.asFea(), m.name)
+ temp += (
+ "\n"
+ + indent
+ + SHIFT * 2
+ + "{} mark @{}".format(a.asFea(), m.name)
+ )
ligs.append(temp)
res += ("\n" + indent + SHIFT + "ligComponent").join(ligs)
res += ";"
@@ -890,22 +1214,38 @@ class MarkLigPosStatement(Statement):
class MarkMarkPosStatement(Statement):
+ """A mark-to-mark positioning rule. The ``baseMarks`` must be a
+ `glyph-containing object`_. The ``marks`` should be a list of
+ (:class:`Anchor`, :class:`MarkClass`) tuples."""
+
def __init__(self, baseMarks, marks, location=None):
Statement.__init__(self, location)
self.baseMarks, self.marks = baseMarks, marks
def build(self, builder):
+ """Calls the builder object's ``add_mark_mark_pos`` callback."""
builder.add_mark_mark_pos(self.location, self.baseMarks.glyphSet(), self.marks)
def asFea(self, indent=""):
res = "pos mark {}".format(self.baseMarks.asFea())
for a, m in self.marks:
- res += " {} mark @{}".format(a.asFea(), m.name)
+ res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
res += ";"
return res
class MultipleSubstStatement(Statement):
+ """A multiple substitution statement.
+
+ Args:
+ prefix: a list of `glyph-containing objects`_.
+ glyph: a single glyph-containing object.
+ suffix: a list of glyph-containing objects.
+ replacement: a list of glyph-containing objects.
+ forceChain: If true, the statement is expressed as a chaining rule
+ (e.g. ``sub f' i' by f_i``) even when no context is given.
+ """
+
def __init__(
self, prefix, glyph, suffix, replacement, forceChain=False, location=None
):
@@ -915,11 +1255,12 @@ class MultipleSubstStatement(Statement):
self.forceChain = forceChain
def build(self, builder):
+ """Calls the builder object's ``add_multiple_subst`` callback."""
prefix = [p.glyphSet() for p in self.prefix]
suffix = [s.glyphSet() for s in self.suffix]
builder.add_multiple_subst(
- self.location, prefix, self.glyph, suffix, self.replacement,
- self.forceChain)
+ self.location, prefix, self.glyph, suffix, self.replacement, self.forceChain
+ )
def asFea(self, indent=""):
res = "sub "
@@ -931,56 +1272,100 @@ class MultipleSubstStatement(Statement):
res += " " + " ".join(map(asFea, self.suffix))
else:
res += asFea(self.glyph)
+ replacement = self.replacement or [NullGlyph()]
res += " by "
- res += " ".join(map(asFea, self.replacement))
+ res += " ".join(map(asFea, replacement))
res += ";"
return res
class PairPosStatement(Statement):
- def __init__(self, glyphs1, valuerecord1, glyphs2, valuerecord2,
- enumerated=False, location=None):
+ """A pair positioning statement.
+
+ ``glyphs1`` and ``glyphs2`` should be `glyph-containing objects`_.
+ ``valuerecord1`` should be a :class:`ValueRecord` object;
+ ``valuerecord2`` should be either a :class:`ValueRecord` object or ``None``.
+ If ``enumerated`` is true, then this is expressed as an
+ `enumerated pair <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#6.b.ii>`_.
+ """
+
+ def __init__(
+ self,
+ glyphs1,
+ valuerecord1,
+ glyphs2,
+ valuerecord2,
+ enumerated=False,
+ location=None,
+ ):
Statement.__init__(self, location)
self.enumerated = enumerated
self.glyphs1, self.valuerecord1 = glyphs1, valuerecord1
self.glyphs2, self.valuerecord2 = glyphs2, valuerecord2
def build(self, builder):
+ """Calls a callback on the builder object:
+
+ * If the rule is enumerated, calls ``add_specific_pair_pos`` on each
+ combination of first and second glyphs.
+ * If the glyphs are both single :class:`GlyphName` objects, calls
+ ``add_specific_pair_pos``.
+ * Else, calls ``add_class_pair_pos``.
+ """
if self.enumerated:
g = [self.glyphs1.glyphSet(), self.glyphs2.glyphSet()]
for glyph1, glyph2 in itertools.product(*g):
builder.add_specific_pair_pos(
- self.location, glyph1, self.valuerecord1,
- glyph2, self.valuerecord2)
+ self.location, glyph1, self.valuerecord1, glyph2, self.valuerecord2
+ )
return
- is_specific = (isinstance(self.glyphs1, GlyphName) and
- isinstance(self.glyphs2, GlyphName))
+ is_specific = isinstance(self.glyphs1, GlyphName) and isinstance(
+ self.glyphs2, GlyphName
+ )
if is_specific:
builder.add_specific_pair_pos(
- self.location, self.glyphs1.glyph, self.valuerecord1,
- self.glyphs2.glyph, self.valuerecord2)
+ self.location,
+ self.glyphs1.glyph,
+ self.valuerecord1,
+ self.glyphs2.glyph,
+ self.valuerecord2,
+ )
else:
builder.add_class_pair_pos(
- self.location, self.glyphs1.glyphSet(), self.valuerecord1,
- self.glyphs2.glyphSet(), self.valuerecord2)
+ self.location,
+ self.glyphs1.glyphSet(),
+ self.valuerecord1,
+ self.glyphs2.glyphSet(),
+ self.valuerecord2,
+ )
def asFea(self, indent=""):
res = "enum " if self.enumerated else ""
if self.valuerecord2:
res += "pos {} {} {} {};".format(
- self.glyphs1.asFea(), self.valuerecord1.asFea(),
- self.glyphs2.asFea(), self.valuerecord2.asFea())
+ self.glyphs1.asFea(),
+ self.valuerecord1.asFea(),
+ self.glyphs2.asFea(),
+ self.valuerecord2.asFea(),
+ )
else:
res += "pos {} {} {};".format(
- self.glyphs1.asFea(), self.glyphs2.asFea(),
- self.valuerecord1.asFea())
+ self.glyphs1.asFea(), self.glyphs2.asFea(), self.valuerecord1.asFea()
+ )
return res
class ReverseChainSingleSubstStatement(Statement):
- def __init__(self, old_prefix, old_suffix, glyphs, replacements,
- location=None):
+ """A reverse chaining substitution statement. You don't see those every day.
+
+ Note the unusual argument order: ``suffix`` comes `before` ``glyphs``.
+ ``old_prefix``, ``old_suffix``, ``glyphs`` and ``replacements`` should be
+ lists of `glyph-containing objects`_. ``glyphs`` and ``replacements`` should
+ be one-item lists.
+ """
+
+ def __init__(self, old_prefix, old_suffix, glyphs, replacements, location=None):
Statement.__init__(self, location)
self.old_prefix, self.old_suffix = old_prefix, old_suffix
self.glyphs = glyphs
@@ -994,7 +1379,8 @@ class ReverseChainSingleSubstStatement(Statement):
if len(replaces) == 1:
replaces = replaces * len(originals)
builder.add_reverse_chain_single_subst(
- self.location, prefix, suffix, dict(zip(originals, replaces)))
+ self.location, prefix, suffix, dict(zip(originals, replaces))
+ )
def asFea(self, indent=""):
res = "rsub "
@@ -1011,8 +1397,15 @@ class ReverseChainSingleSubstStatement(Statement):
class SingleSubstStatement(Statement):
- def __init__(self, glyphs, replace, prefix, suffix, forceChain,
- location=None):
+ """A single substitution statement.
+
+ Note the unusual argument order: ``prefix`` and suffix come `after`
+ the replacement ``glyphs``. ``prefix``, ``suffix``, ``glyphs`` and
+ ``replace`` should be lists of `glyph-containing objects`_. ``glyphs`` and
+ ``replace`` should be one-item lists.
+ """
+
+ def __init__(self, glyphs, replace, prefix, suffix, forceChain, location=None):
Statement.__init__(self, location)
self.prefix, self.suffix = prefix, suffix
self.forceChain = forceChain
@@ -1020,15 +1413,20 @@ class SingleSubstStatement(Statement):
self.replacements = replace
def build(self, builder):
+ """Calls the builder object's ``add_single_subst`` callback."""
prefix = [p.glyphSet() for p in self.prefix]
suffix = [s.glyphSet() for s in self.suffix]
originals = self.glyphs[0].glyphSet()
replaces = self.replacements[0].glyphSet()
if len(replaces) == 1:
replaces = replaces * len(originals)
- builder.add_single_subst(self.location, prefix, suffix,
- OrderedDict(zip(originals, replaces)),
- self.forceChain)
+ builder.add_single_subst(
+ self.location,
+ prefix,
+ suffix,
+ OrderedDict(zip(originals, replaces)),
+ self.forceChain,
+ )
def asFea(self, indent=""):
res = "sub "
@@ -1045,11 +1443,14 @@ class SingleSubstStatement(Statement):
class ScriptStatement(Statement):
+ """A ``script`` statement."""
+
def __init__(self, script, location=None):
Statement.__init__(self, location)
- self.script = script
+ self.script = script #: the script code
def build(self, builder):
+ """Calls the builder's ``set_script`` callback."""
builder.set_script(self.location, self.script)
def asFea(self, indent=""):
@@ -1057,39 +1458,53 @@ class ScriptStatement(Statement):
class SinglePosStatement(Statement):
+ """A single position statement. ``prefix`` and ``suffix`` should be
+ lists of `glyph-containing objects`_.
+
+ ``pos`` should be a one-element list containing a (`glyph-containing object`_,
+ :class:`ValueRecord`) tuple."""
+
def __init__(self, pos, prefix, suffix, forceChain, location=None):
Statement.__init__(self, location)
self.pos, self.prefix, self.suffix = pos, prefix, suffix
self.forceChain = forceChain
def build(self, builder):
+ """Calls the builder object's ``add_single_pos`` callback."""
prefix = [p.glyphSet() for p in self.prefix]
suffix = [s.glyphSet() for s in self.suffix]
pos = [(g.glyphSet(), value) for g, value in self.pos]
- builder.add_single_pos(self.location, prefix, suffix,
- pos, self.forceChain)
+ builder.add_single_pos(self.location, prefix, suffix, pos, self.forceChain)
def asFea(self, indent=""):
res = "pos "
if len(self.prefix) or len(self.suffix) or self.forceChain:
if len(self.prefix):
res += " ".join(map(asFea, self.prefix)) + " "
- res += " ".join([asFea(x[0]) + "'" + (
- (" " + x[1].asFea()) if x[1] else "") for x in self.pos])
+ res += " ".join(
+ [
+ asFea(x[0]) + "'" + ((" " + x[1].asFea()) if x[1] else "")
+ for x in self.pos
+ ]
+ )
if len(self.suffix):
res += " " + " ".join(map(asFea, self.suffix))
else:
- res += " ".join([asFea(x[0]) + " " +
- (x[1].asFea() if x[1] else "") for x in self.pos])
+ res += " ".join(
+ [asFea(x[0]) + " " + (x[1].asFea() if x[1] else "") for x in self.pos]
+ )
res += ";"
return res
class SubtableStatement(Statement):
+ """Represents a subtable break."""
+
def __init__(self, location=None):
Statement.__init__(self, location)
def build(self, builder):
+ """Calls the builder objects's ``add_subtable_break`` callback."""
builder.add_subtable_break(self.location)
def asFea(self, indent=""):
@@ -1097,11 +1512,21 @@ class SubtableStatement(Statement):
class ValueRecord(Expression):
- def __init__(self, xPlacement=None, yPlacement=None,
- xAdvance=None, yAdvance=None,
- xPlaDevice=None, yPlaDevice=None,
- xAdvDevice=None, yAdvDevice=None,
- vertical=False, location=None):
+ """Represents a value record."""
+
+ def __init__(
+ self,
+ xPlacement=None,
+ yPlacement=None,
+ xAdvance=None,
+ yAdvance=None,
+ xPlaDevice=None,
+ yPlaDevice=None,
+ xAdvDevice=None,
+ yAdvDevice=None,
+ vertical=False,
+ location=None,
+ ):
Expression.__init__(self, location)
self.xPlacement, self.yPlacement = (xPlacement, yPlacement)
self.xAdvance, self.yAdvance = (xAdvance, yAdvance)
@@ -1110,21 +1535,29 @@ class ValueRecord(Expression):
self.vertical = vertical
def __eq__(self, other):
- return (self.xPlacement == other.xPlacement and
- self.yPlacement == other.yPlacement and
- self.xAdvance == other.xAdvance and
- self.yAdvance == other.yAdvance and
- self.xPlaDevice == other.xPlaDevice and
- self.xAdvDevice == other.xAdvDevice)
+ return (
+ self.xPlacement == other.xPlacement
+ and self.yPlacement == other.yPlacement
+ and self.xAdvance == other.xAdvance
+ and self.yAdvance == other.yAdvance
+ and self.xPlaDevice == other.xPlaDevice
+ and self.xAdvDevice == other.xAdvDevice
+ )
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
- return (hash(self.xPlacement) ^ hash(self.yPlacement) ^
- hash(self.xAdvance) ^ hash(self.yAdvance) ^
- hash(self.xPlaDevice) ^ hash(self.yPlaDevice) ^
- hash(self.xAdvDevice) ^ hash(self.yAdvDevice))
+ return (
+ hash(self.xPlacement)
+ ^ hash(self.yPlacement)
+ ^ hash(self.xAdvance)
+ ^ hash(self.yAdvance)
+ ^ hash(self.xPlaDevice)
+ ^ hash(self.yPlaDevice)
+ ^ hash(self.xAdvDevice)
+ ^ hash(self.yAdvDevice)
+ )
def asFea(self, indent=""):
if not self:
@@ -1150,15 +1583,25 @@ class ValueRecord(Expression):
yAdvance = yAdvance or 0
# Try format B, if possible.
- if (xPlaDevice is None and yPlaDevice is None and
- xAdvDevice is None and yAdvDevice is None):
+ if (
+ xPlaDevice is None
+ and yPlaDevice is None
+ and xAdvDevice is None
+ and yAdvDevice is None
+ ):
return "<%s %s %s %s>" % (x, y, xAdvance, yAdvance)
# Last resort is format C.
return "<%s %s %s %s %s %s %s %s>" % (
- x, y, xAdvance, yAdvance,
- deviceToString(xPlaDevice), deviceToString(yPlaDevice),
- deviceToString(xAdvDevice), deviceToString(yAdvDevice))
+ x,
+ y,
+ xAdvance,
+ yAdvance,
+ deviceToString(xPlaDevice),
+ deviceToString(yPlaDevice),
+ deviceToString(xAdvDevice),
+ deviceToString(yAdvDevice),
+ )
def __bool__(self):
return any(
@@ -1179,10 +1622,12 @@ class ValueRecord(Expression):
class ValueRecordDefinition(Statement):
+ """Represents a named value record definition."""
+
def __init__(self, name, value, location=None):
Statement.__init__(self, location)
- self.name = name
- self.value = value
+ self.name = name #: Value record name as string
+ self.value = value #: :class:`ValueRecord` object
def asFea(self, indent=""):
return "valueRecordDef {} {};".format(self.value.asFea(), self.name)
@@ -1198,46 +1643,59 @@ def simplify_name_attributes(pid, eid, lid):
class NameRecord(Statement):
- def __init__(self, nameID, platformID, platEncID, langID, string,
- location=None):
+ """Represents a name record. (`Section 9.e. <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.e>`_)"""
+
+ def __init__(self, nameID, platformID, platEncID, langID, string, location=None):
Statement.__init__(self, location)
- self.nameID = nameID
- self.platformID = platformID
- self.platEncID = platEncID
- self.langID = langID
- self.string = string
+ self.nameID = nameID #: Name ID as integer (e.g. 9 for designer's name)
+ self.platformID = platformID #: Platform ID as integer
+ self.platEncID = platEncID #: Platform encoding ID as integer
+ self.langID = langID #: Language ID as integer
+ self.string = string #: Name record value
def build(self, builder):
+ """Calls the builder object's ``add_name_record`` callback."""
builder.add_name_record(
- self.location, self.nameID, self.platformID,
- self.platEncID, self.langID, self.string)
+ self.location,
+ self.nameID,
+ self.platformID,
+ self.platEncID,
+ self.langID,
+ self.string,
+ )
def asFea(self, indent=""):
def escape(c, escape_pattern):
# Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS
if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C):
- return unichr(c)
+ return chr(c)
else:
return escape_pattern % c
+
encoding = getEncoding(self.platformID, self.platEncID, self.langID)
if encoding is None:
raise FeatureLibError("Unsupported encoding", self.location)
s = tobytes(self.string, encoding=encoding)
if encoding == "utf_16_be":
- escaped_string = "".join([
- escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x")
- for i in range(0, len(s), 2)])
+ escaped_string = "".join(
+ [
+ escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x")
+ for i in range(0, len(s), 2)
+ ]
+ )
else:
escaped_string = "".join([escape(byteord(b), r"\%02x") for b in s])
- plat = simplify_name_attributes(
- self.platformID, self.platEncID, self.langID)
+ plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
if plat != "":
plat += " "
- return "nameid {} {}\"{}\";".format(self.nameID, plat, escaped_string)
+ return 'nameid {} {}"{}";'.format(self.nameID, plat, escaped_string)
class FeatureNameStatement(NameRecord):
+ """Represents a ``sizemenuname`` or ``name`` statement."""
+
def build(self, builder):
+ """Calls the builder object's ``add_featureName`` callback."""
NameRecord.build(self, builder)
builder.add_featureName(self.nameID)
@@ -1249,12 +1707,23 @@ class FeatureNameStatement(NameRecord):
plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
if plat != "":
plat += " "
- return "{} {}\"{}\";".format(tag, plat, self.string)
+ return '{} {}"{}";'.format(tag, plat, self.string)
+
+
+class STATNameStatement(NameRecord):
+ """Represents a STAT table ``name`` statement."""
+
+ def asFea(self, indent=""):
+ plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
+ if plat != "":
+ plat += " "
+ return 'name {}"{}";'.format(plat, self.string)
class SizeParameters(Statement):
- def __init__(self, DesignSize, SubfamilyID, RangeStart, RangeEnd,
- location=None):
+ """A ``parameters`` statement."""
+
+ def __init__(self, DesignSize, SubfamilyID, RangeStart, RangeEnd, location=None):
Statement.__init__(self, location)
self.DesignSize = DesignSize
self.SubfamilyID = SubfamilyID
@@ -1262,8 +1731,14 @@ class SizeParameters(Statement):
self.RangeEnd = RangeEnd
def build(self, builder):
- builder.set_size_parameters(self.location, self.DesignSize,
- self.SubfamilyID, self.RangeStart, self.RangeEnd)
+ """Calls the builder object's ``set_size_parameters`` callback."""
+ builder.set_size_parameters(
+ self.location,
+ self.DesignSize,
+ self.SubfamilyID,
+ self.RangeStart,
+ self.RangeEnd,
+ )
def asFea(self, indent=""):
res = "parameters {:.1f} {}".format(self.DesignSize, self.SubfamilyID)
@@ -1273,13 +1748,18 @@ class SizeParameters(Statement):
class CVParametersNameStatement(NameRecord):
- def __init__(self, nameID, platformID, platEncID, langID, string,
- block_name, location=None):
- NameRecord.__init__(self, nameID, platformID, platEncID, langID,
- string, location=location)
+ """Represent a name statement inside a ``cvParameters`` block."""
+
+ def __init__(
+ self, nameID, platformID, platEncID, langID, string, block_name, location=None
+ ):
+ NameRecord.__init__(
+ self, nameID, platformID, platEncID, langID, string, location=location
+ )
self.block_name = block_name
def build(self, builder):
+ """Calls the builder object's ``add_cv_parameter`` callback."""
item = ""
if self.block_name == "ParamUILabelNameID":
item = "_{}".format(builder.cv_num_named_params_.get(self.nameID, 0))
@@ -1288,11 +1768,10 @@ class CVParametersNameStatement(NameRecord):
NameRecord.build(self, builder)
def asFea(self, indent=""):
- plat = simplify_name_attributes(self.platformID, self.platEncID,
- self.langID)
+ plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
if plat != "":
plat += " "
- return "name {}\"{}\";".format(plat, self.string)
+ return 'name {}"{}";'.format(plat, self.string)
class CharacterStatement(Statement):
@@ -1302,12 +1781,14 @@ class CharacterStatement(Statement):
notation. The value must be preceded by '0x' if it is a hexadecimal value.
The largest Unicode value allowed is 0xFFFFFF.
"""
+
def __init__(self, character, tag, location=None):
Statement.__init__(self, location)
self.character = character
self.tag = tag
def build(self, builder):
+ """Calls the builder object's ``add_cv_character`` callback."""
builder.add_cv_character(self.character, self.tag)
def asFea(self, indent=""):
@@ -1315,54 +1796,84 @@ class CharacterStatement(Statement):
class BaseAxis(Statement):
+ """An axis definition, being either a ``VertAxis.BaseTagList/BaseScriptList``
+ pair or a ``HorizAxis.BaseTagList/BaseScriptList`` pair."""
+
def __init__(self, bases, scripts, vertical, location=None):
Statement.__init__(self, location)
- self.bases = bases
- self.scripts = scripts
- self.vertical = vertical
+ self.bases = bases #: A list of baseline tag names as strings
+ self.scripts = scripts #: A list of script record tuplets (script tag, default baseline tag, base coordinate)
+ self.vertical = vertical #: Boolean; VertAxis if True, HorizAxis if False
def build(self, builder):
+ """Calls the builder object's ``set_base_axis`` callback."""
builder.set_base_axis(self.bases, self.scripts, self.vertical)
def asFea(self, indent=""):
direction = "Vert" if self.vertical else "Horiz"
- scripts = ["{} {} {}".format(a[0], a[1], " ".join(map(str, a[2]))) for a in self.scripts]
+ scripts = [
+ "{} {} {}".format(a[0], a[1], " ".join(map(str, a[2])))
+ for a in self.scripts
+ ]
return "{}Axis.BaseTagList {};\n{}{}Axis.BaseScriptList {};".format(
- direction, " ".join(self.bases), indent, direction, ", ".join(scripts))
+ direction, " ".join(self.bases), indent, direction, ", ".join(scripts)
+ )
class OS2Field(Statement):
+ """An entry in the ``OS/2`` table. Most ``values`` should be numbers or
+ strings, apart from when the key is ``UnicodeRange``, ``CodePageRange``
+ or ``Panose``, in which case it should be an array of integers."""
+
def __init__(self, key, value, location=None):
Statement.__init__(self, location)
self.key = key
self.value = value
def build(self, builder):
+ """Calls the builder object's ``add_os2_field`` callback."""
builder.add_os2_field(self.key, self.value)
def asFea(self, indent=""):
def intarr2str(x):
return " ".join(map(str, x))
- numbers = ("FSType", "TypoAscender", "TypoDescender", "TypoLineGap",
- "winAscent", "winDescent", "XHeight", "CapHeight",
- "WeightClass", "WidthClass", "LowerOpSize", "UpperOpSize")
+
+ numbers = (
+ "FSType",
+ "TypoAscender",
+ "TypoDescender",
+ "TypoLineGap",
+ "winAscent",
+ "winDescent",
+ "XHeight",
+ "CapHeight",
+ "WeightClass",
+ "WidthClass",
+ "LowerOpSize",
+ "UpperOpSize",
+ )
ranges = ("UnicodeRange", "CodePageRange")
keywords = dict([(x.lower(), [x, str]) for x in numbers])
keywords.update([(x.lower(), [x, intarr2str]) for x in ranges])
keywords["panose"] = ["Panose", intarr2str]
keywords["vendor"] = ["Vendor", lambda y: '"{}"'.format(y)]
if self.key in keywords:
- return "{} {};".format(keywords[self.key][0], keywords[self.key][1](self.value))
- return "" # should raise exception
+ return "{} {};".format(
+ keywords[self.key][0], keywords[self.key][1](self.value)
+ )
+ return "" # should raise exception
class HheaField(Statement):
+ """An entry in the ``hhea`` table."""
+
def __init__(self, key, value, location=None):
Statement.__init__(self, location)
self.key = key
self.value = value
def build(self, builder):
+ """Calls the builder object's ``add_hhea_field`` callback."""
builder.add_hhea_field(self.key, self.value)
def asFea(self, indent=""):
@@ -1372,15 +1883,147 @@ class HheaField(Statement):
class VheaField(Statement):
+ """An entry in the ``vhea`` table."""
+
def __init__(self, key, value, location=None):
Statement.__init__(self, location)
self.key = key
self.value = value
def build(self, builder):
+ """Calls the builder object's ``add_vhea_field`` callback."""
builder.add_vhea_field(self.key, self.value)
def asFea(self, indent=""):
fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap")
keywords = dict([(x.lower(), x) for x in fields])
return "{} {};".format(keywords[self.key], self.value)
+
+
+class STATDesignAxisStatement(Statement):
+ """A STAT table Design Axis
+
+ Args:
+ tag (str): a 4 letter axis tag
+ axisOrder (int): an int
+ names (list): a list of :class:`STATNameStatement` objects
+ """
+
+ def __init__(self, tag, axisOrder, names, location=None):
+ Statement.__init__(self, location)
+ self.tag = tag
+ self.axisOrder = axisOrder
+ self.names = names
+ self.location = location
+
+ def build(self, builder):
+ builder.addDesignAxis(self, self.location)
+
+ def asFea(self, indent=""):
+ indent += SHIFT
+ res = f"DesignAxis {self.tag} {self.axisOrder} {{ \n"
+ res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
+ res += "};"
+ return res
+
+
+class ElidedFallbackName(Statement):
+ """STAT table ElidedFallbackName
+
+ Args:
+ names: a list of :class:`STATNameStatement` objects
+ """
+
+ def __init__(self, names, location=None):
+ Statement.__init__(self, location)
+ self.names = names
+ self.location = location
+
+ def build(self, builder):
+ builder.setElidedFallbackName(self.names, self.location)
+
+ def asFea(self, indent=""):
+ indent += SHIFT
+ res = "ElidedFallbackName { \n"
+ res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
+ res += "};"
+ return res
+
+
+class ElidedFallbackNameID(Statement):
+ """STAT table ElidedFallbackNameID
+
+ Args:
+ value: an int pointing to an existing name table name ID
+ """
+
+ def __init__(self, value, location=None):
+ Statement.__init__(self, location)
+ self.value = value
+ self.location = location
+
+ def build(self, builder):
+ builder.setElidedFallbackName(self.value, self.location)
+
+ def asFea(self, indent=""):
+ return f"ElidedFallbackNameID {self.value};"
+
+
+class STATAxisValueStatement(Statement):
+ """A STAT table Axis Value Record
+
+ Args:
+ names (list): a list of :class:`STATNameStatement` objects
+ locations (list): a list of :class:`AxisValueLocationStatement` objects
+ flags (int): an int
+ """
+
+ def __init__(self, names, locations, flags, location=None):
+ Statement.__init__(self, location)
+ self.names = names
+ self.locations = locations
+ self.flags = flags
+
+ def build(self, builder):
+ builder.addAxisValueRecord(self, self.location)
+
+ def asFea(self, indent=""):
+ res = "AxisValue {\n"
+ for location in self.locations:
+ res += location.asFea()
+
+ for nameRecord in self.names:
+ res += nameRecord.asFea()
+ res += "\n"
+
+ if self.flags:
+ flags = ["OlderSiblingFontAttribute", "ElidableAxisValueName"]
+ flagStrings = []
+ curr = 1
+ for i in range(len(flags)):
+ if self.flags & curr != 0:
+ flagStrings.append(flags[i])
+ curr = curr << 1
+ res += f"flag {' '.join(flagStrings)};\n"
+ res += "};"
+ return res
+
+
+class AxisValueLocationStatement(Statement):
+ """
+ A STAT table Axis Value Location
+
+ Args:
+ tag (str): a 4 letter axis tag
+ values (list): a list of ints and/or floats
+ """
+
+ def __init__(self, tag, values, location=None):
+ Statement.__init__(self, location)
+ self.tag = tag
+ self.values = values
+
+ def asFea(self, res=""):
+ res += f"location {self.tag} "
+ res += f"{' '.join(str(i) for i in self.values)};\n"
+ return res
diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py
index 8880acf1..4a7d9575 100644
--- a/Lib/fontTools/feaLib/builder.py
+++ b/Lib/fontTools/feaLib/builder.py
@@ -1,50 +1,107 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, tostr
from fontTools.misc import sstruct
from fontTools.misc.textTools import binary2num, safeEval
from fontTools.feaLib.error import FeatureLibError
+from fontTools.feaLib.lookupDebugInfo import (
+ LookupDebugInfo,
+ LOOKUP_DEBUG_INFO_KEY,
+ LOOKUP_DEBUG_ENV_VAR,
+)
from fontTools.feaLib.parser import Parser
from fontTools.feaLib.ast import FeatureFile
from fontTools.otlLib import builder as otl
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.ttLib import newTable, getTableModule
from fontTools.ttLib.tables import otBase, otTables
-from collections import defaultdict, OrderedDict
+from fontTools.otlLib.builder import (
+ AlternateSubstBuilder,
+ ChainContextPosBuilder,
+ ChainContextSubstBuilder,
+ LigatureSubstBuilder,
+ MultipleSubstBuilder,
+ CursivePosBuilder,
+ MarkBasePosBuilder,
+ MarkLigPosBuilder,
+ MarkMarkPosBuilder,
+ ReverseChainSingleSubstBuilder,
+ SingleSubstBuilder,
+ ClassPairPosSubtableBuilder,
+ PairPosBuilder,
+ SinglePosBuilder,
+ ChainContextualRule,
+)
+from fontTools.otlLib.error import OpenTypeLibError
+from collections import defaultdict
import itertools
+from io import StringIO
import logging
+import warnings
+import os
log = logging.getLogger(__name__)
-def addOpenTypeFeatures(font, featurefile, tables=None):
+def addOpenTypeFeatures(font, featurefile, tables=None, debug=False):
+ """Add features from a file to a font. Note that this replaces any features
+ currently present.
+
+ Args:
+ font (feaLib.ttLib.TTFont): The font object.
+ featurefile: Either a path or file object (in which case we
+ parse it into an AST), or a pre-parsed AST instance.
+ tables: If passed, restrict the set of affected tables to those in the
+ list.
+ debug: Whether to add source debugging information to the font in the
+ ``Debg`` table
+
+ """
builder = Builder(font, featurefile)
- builder.build(tables=tables)
+ builder.build(tables=tables, debug=debug)
+
+
+def addOpenTypeFeaturesFromString(
+ font, features, filename=None, tables=None, debug=False
+):
+ """Add features from a string to a font. Note that this replaces any
+ features currently present.
+
+ Args:
+ font (feaLib.ttLib.TTFont): The font object.
+ features: A string containing feature code.
+ filename: The directory containing ``filename`` is used as the root of
+ relative ``include()`` paths; if ``None`` is provided, the current
+ directory is assumed.
+ tables: If passed, restrict the set of affected tables to those in the
+ list.
+ debug: Whether to add source debugging information to the font in the
+ ``Debg`` table
+ """
-def addOpenTypeFeaturesFromString(font, features, filename=None, tables=None):
- featurefile = UnicodeIO(tounicode(features))
+ featurefile = StringIO(tostr(features))
if filename:
- # the directory containing 'filename' is used as the root of relative
- # include paths; if None is provided, the current directory is assumed
featurefile.name = filename
- addOpenTypeFeatures(font, featurefile, tables=tables)
+ addOpenTypeFeatures(font, featurefile, tables=tables, debug=debug)
class Builder(object):
- supportedTables = frozenset(Tag(tag) for tag in [
- "BASE",
- "GDEF",
- "GPOS",
- "GSUB",
- "OS/2",
- "head",
- "hhea",
- "name",
- "vhea",
- ])
+ supportedTables = frozenset(
+ Tag(tag)
+ for tag in [
+ "BASE",
+ "GDEF",
+ "GPOS",
+ "GSUB",
+ "OS/2",
+ "head",
+ "hhea",
+ "name",
+ "vhea",
+ "STAT",
+ ]
+ )
def __init__(self, font, featurefile):
self.font = font
@@ -66,6 +123,7 @@ class Builder(object):
self.cur_lookup_name_ = None
self.cur_feature_name_ = None
self.lookups_ = []
+ self.lookup_locations = {"GSUB": {}, "GPOS": {}}
self.features_ = {} # ('latn', 'DEU ', 'smcp') --> [LookupBuilder*]
self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp'
# for feature 'aalt'
@@ -103,8 +161,10 @@ class Builder(object):
self.hhea_ = {}
# for table 'vhea'
self.vhea_ = {}
+ # for table 'STAT'
+ self.stat_ = {}
- def build(self, tables=None):
+ def build(self, tables=None, debug=False):
if self.parseTree is None:
self.parseTree = Parser(self.file, self.glyphMap).parse()
self.parseTree.build(self)
@@ -114,7 +174,12 @@ class Builder(object):
else:
tables = frozenset(tables)
unsupported = tables - self.supportedTables
- assert not unsupported, unsupported
+ if unsupported:
+ unsupported_string = ", ".join(sorted(unsupported))
+ raise NotImplementedError(
+ "The following tables were requested but are unsupported: "
+ f"{unsupported_string}."
+ )
if "GSUB" in tables:
self.build_feature_aalt_()
if "head" in tables:
@@ -127,19 +192,22 @@ class Builder(object):
self.build_name()
if "OS/2" in tables:
self.build_OS_2()
- for tag in ('GPOS', 'GSUB'):
+ if "STAT" in tables:
+ self.build_STAT()
+ for tag in ("GPOS", "GSUB"):
if tag not in tables:
continue
table = self.makeTable(tag)
- if (table.ScriptList.ScriptCount > 0 or
- table.FeatureList.FeatureCount > 0 or
- table.LookupList.LookupCount > 0):
+ if (
+ table.ScriptList.ScriptCount > 0
+ or table.FeatureList.FeatureCount > 0
+ or table.LookupList.LookupCount > 0
+ ):
fontTable = self.font[tag] = newTable(tag)
fontTable.table = table
elif tag in self.font:
del self.font[tag]
- if (any(tag in self.font for tag in ("GPOS", "GSUB")) and
- "OS/2" in self.font):
+ if any(tag in self.font for tag in ("GPOS", "GSUB")) and "OS/2" in self.font:
self.font["OS/2"].usMaxContext = maxCtxFont(self.font)
if "GDEF" in tables:
gdef = self.buildGDEF()
@@ -153,6 +221,8 @@ class Builder(object):
self.font["BASE"] = base
elif "BASE" in self.font:
del self.font["BASE"]
+ if debug or os.environ.get(LOOKUP_DEBUG_ENV_VAR):
+ self.buildDebg()
def get_chained_lookup_(self, location, builder_class):
result = builder_class(self.font, location)
@@ -167,16 +237,19 @@ class Builder(object):
self.features_.setdefault(key, []).append(lookup)
def get_lookup_(self, location, builder_class):
- if (self.cur_lookup_ and
- type(self.cur_lookup_) == builder_class and
- self.cur_lookup_.lookupflag == self.lookupflag_ and
- self.cur_lookup_.markFilterSet ==
- self.lookupflag_markFilterSet_):
+ if (
+ self.cur_lookup_
+ and type(self.cur_lookup_) == builder_class
+ and self.cur_lookup_.lookupflag == self.lookupflag_
+ and self.cur_lookup_.markFilterSet == self.lookupflag_markFilterSet_
+ ):
return self.cur_lookup_
if self.cur_lookup_name_ and self.cur_lookup_:
raise FeatureLibError(
"Within a named lookup block, all rules must be of "
- "the same lookup type and flag", location)
+ "the same lookup type and flag",
+ location,
+ )
self.cur_lookup_ = builder_class(self.font, location)
self.cur_lookup_.lookupflag = self.lookupflag_
self.cur_lookup_.markFilterSet = self.lookupflag_markFilterSet_
@@ -187,8 +260,7 @@ class Builder(object):
if self.cur_feature_name_:
# We are starting a lookup rule inside a feature. This includes
# lookup rules inside named lookups inside features.
- self.add_lookup_to_feature_(self.cur_lookup_,
- self.cur_feature_name_)
+ self.add_lookup_to_feature_(self.cur_lookup_, self.cur_feature_name_)
return self.cur_lookup_
def build_feature_aalt_(self):
@@ -196,31 +268,40 @@ class Builder(object):
return
alternates = {g: set(a) for g, a in self.aalt_alternates_.items()}
for location, name in self.aalt_features_ + [(None, "aalt")]:
- feature = [(script, lang, feature, lookups)
- for (script, lang, feature), lookups
- in self.features_.items()
- if feature == name]
+ feature = [
+ (script, lang, feature, lookups)
+ for (script, lang, feature), lookups in self.features_.items()
+ if feature == name
+ ]
# "aalt" does not have to specify its own lookups, but it might.
if not feature and name != "aalt":
- raise FeatureLibError("Feature %s has not been defined" % name,
- location)
+ raise FeatureLibError(
+ "Feature %s has not been defined" % name, location
+ )
for script, lang, feature, lookups in feature:
- for lookup in lookups:
- for glyph, alts in lookup.getAlternateGlyphs().items():
- alternates.setdefault(glyph, set()).update(alts)
- single = {glyph: list(repl)[0] for glyph, repl in alternates.items()
- if len(repl) == 1}
+ for lookuplist in lookups:
+ if not isinstance(lookuplist, list):
+ lookuplist = [lookuplist]
+ for lookup in lookuplist:
+ for glyph, alts in lookup.getAlternateGlyphs().items():
+ alternates.setdefault(glyph, set()).update(alts)
+ single = {
+ glyph: list(repl)[0] for glyph, repl in alternates.items() if len(repl) == 1
+ }
# TODO: Figure out the glyph alternate ordering used by makeotf.
# https://github.com/fonttools/fonttools/issues/836
- multi = {glyph: sorted(repl, key=self.font.getGlyphID)
- for glyph, repl in alternates.items()
- if len(repl) > 1}
+ multi = {
+ glyph: sorted(repl, key=self.font.getGlyphID)
+ for glyph, repl in alternates.items()
+ if len(repl) > 1
+ }
if not single and not multi:
return
- self.features_ = {(script, lang, feature): lookups
- for (script, lang, feature), lookups
- in self.features_.items()
- if feature != "aalt"}
+ self.features_ = {
+ (script, lang, feature): lookups
+ for (script, lang, feature), lookups in self.features_.items()
+ if feature != "aalt"
+ }
old_lookups = self.lookups_
self.lookups_ = []
self.start_feature(self.aalt_location_, "aalt")
@@ -287,8 +368,12 @@ class Builder(object):
params = None
if tag == "size":
params = otTables.FeatureParamsSize()
- params.DesignSize, params.SubfamilyID, params.RangeStart, \
- params.RangeEnd = self.size_parameters_
+ (
+ params.DesignSize,
+ params.SubfamilyID,
+ params.RangeStart,
+ params.RangeEnd,
+ ) = self.size_parameters_
if tag in self.featureNames_ids_:
params.SubfamilyNameID = self.featureNames_ids_[tag]
else:
@@ -306,14 +391,18 @@ class Builder(object):
params = otTables.FeatureParamsCharacterVariants()
params.Format = 0
params.FeatUILabelNameID = self.cv_parameters_ids_.get(
- (tag, 'FeatUILabelNameID'), 0)
+ (tag, "FeatUILabelNameID"), 0
+ )
params.FeatUITooltipTextNameID = self.cv_parameters_ids_.get(
- (tag, 'FeatUITooltipTextNameID'), 0)
+ (tag, "FeatUITooltipTextNameID"), 0
+ )
params.SampleTextNameID = self.cv_parameters_ids_.get(
- (tag, 'SampleTextNameID'), 0)
+ (tag, "SampleTextNameID"), 0
+ )
params.NumNamedParameters = self.cv_num_named_params_.get(tag, 0)
params.FirstParamUILabelNameID = self.cv_parameters_ids_.get(
- (tag, 'ParamUILabelNameID_0'), 0)
+ (tag, "ParamUILabelNameID_0"), 0
+ )
params.CharCount = len(self.cv_characters_[tag])
params.Character = self.cv_characters_[tag]
return params
@@ -356,10 +445,18 @@ class Builder(object):
table.fsType = self.os2_["fstype"]
if "panose" in self.os2_:
panose = getTableModule("OS/2").Panose()
- panose.bFamilyType, panose.bSerifStyle, panose.bWeight,\
- panose.bProportion, panose.bContrast, panose.bStrokeVariation,\
- panose.bArmStyle, panose.bLetterForm, panose.bMidline, \
- panose.bXHeight = self.os2_["panose"]
+ (
+ panose.bFamilyType,
+ panose.bSerifStyle,
+ panose.bWeight,
+ panose.bProportion,
+ panose.bContrast,
+ panose.bStrokeVariation,
+ panose.bArmStyle,
+ panose.bLetterForm,
+ panose.bMidline,
+ panose.bXHeight,
+ ) = self.os2_["panose"]
table.panose = panose
if "typoascender" in self.os2_:
table.sTypoAscender = self.os2_["typoascender"]
@@ -395,28 +492,197 @@ class Builder(object):
if "upperopsize" in self.os2_:
table.usUpperOpticalPointSize = self.os2_["upperopsize"]
version = 5
+
def checkattr(table, attrs):
for attr in attrs:
if not hasattr(table, attr):
setattr(table, attr, 0)
+
table.version = max(version, table.version)
# this only happens for unit tests
if version >= 1:
checkattr(table, ("ulCodePageRange1", "ulCodePageRange2"))
if version >= 2:
- checkattr(table, ("sxHeight", "sCapHeight", "usDefaultChar",
- "usBreakChar", "usMaxContext"))
+ checkattr(
+ table,
+ (
+ "sxHeight",
+ "sCapHeight",
+ "usDefaultChar",
+ "usBreakChar",
+ "usMaxContext",
+ ),
+ )
if version >= 5:
- checkattr(table, ("usLowerOpticalPointSize",
- "usUpperOpticalPointSize"))
+ checkattr(table, ("usLowerOpticalPointSize", "usUpperOpticalPointSize"))
+
+ def setElidedFallbackName(self, value, location):
+ # ElidedFallbackName is a convenience method for setting
+ # ElidedFallbackNameID so only one can be allowed
+ for token in ("ElidedFallbackName", "ElidedFallbackNameID"):
+ if token in self.stat_:
+ raise FeatureLibError(
+ f"{token} is already set.",
+ location,
+ )
+ if isinstance(value, int):
+ self.stat_["ElidedFallbackNameID"] = value
+ elif isinstance(value, list):
+ self.stat_["ElidedFallbackName"] = value
+ else:
+ raise AssertionError(value)
+
+ def addDesignAxis(self, designAxis, location):
+ if "DesignAxes" not in self.stat_:
+ self.stat_["DesignAxes"] = []
+ if designAxis.tag in (r.tag for r in self.stat_["DesignAxes"]):
+ raise FeatureLibError(
+ f'DesignAxis already defined for tag "{designAxis.tag}".',
+ location,
+ )
+ if designAxis.axisOrder in (r.axisOrder for r in self.stat_["DesignAxes"]):
+ raise FeatureLibError(
+ f"DesignAxis already defined for axis number {designAxis.axisOrder}.",
+ location,
+ )
+ self.stat_["DesignAxes"].append(designAxis)
+
+ def addAxisValueRecord(self, axisValueRecord, location):
+ if "AxisValueRecords" not in self.stat_:
+ self.stat_["AxisValueRecords"] = []
+ # Check for duplicate AxisValueRecords
+ for record_ in self.stat_["AxisValueRecords"]:
+ if (
+ {n.asFea() for n in record_.names}
+ == {n.asFea() for n in axisValueRecord.names}
+ and {n.asFea() for n in record_.locations}
+ == {n.asFea() for n in axisValueRecord.locations}
+ and record_.flags == axisValueRecord.flags
+ ):
+ raise FeatureLibError(
+ "An AxisValueRecord with these values is already defined.",
+ location,
+ )
+ self.stat_["AxisValueRecords"].append(axisValueRecord)
+
+ def build_STAT(self):
+ if not self.stat_:
+ return
+
+ axes = self.stat_.get("DesignAxes")
+ if not axes:
+ raise FeatureLibError("DesignAxes not defined", None)
+ axisValueRecords = self.stat_.get("AxisValueRecords")
+ axisValues = {}
+ format4_locations = []
+ for tag in axes:
+ axisValues[tag.tag] = []
+ if axisValueRecords is not None:
+ for avr in axisValueRecords:
+ valuesDict = {}
+ if avr.flags > 0:
+ valuesDict["flags"] = avr.flags
+ if len(avr.locations) == 1:
+ location = avr.locations[0]
+ values = location.values
+ if len(values) == 1: # format1
+ valuesDict.update({"value": values[0], "name": avr.names})
+ if len(values) == 2: # format3
+ valuesDict.update(
+ {
+ "value": values[0],
+ "linkedValue": values[1],
+ "name": avr.names,
+ }
+ )
+ if len(values) == 3: # format2
+ nominal, minVal, maxVal = values
+ valuesDict.update(
+ {
+ "nominalValue": nominal,
+ "rangeMinValue": minVal,
+ "rangeMaxValue": maxVal,
+ "name": avr.names,
+ }
+ )
+ axisValues[location.tag].append(valuesDict)
+ else:
+ valuesDict.update(
+ {
+ "location": {i.tag: i.values[0] for i in avr.locations},
+ "name": avr.names,
+ }
+ )
+ format4_locations.append(valuesDict)
+
+ designAxes = [
+ {
+ "ordering": a.axisOrder,
+ "tag": a.tag,
+ "name": a.names,
+ "values": axisValues[a.tag],
+ }
+ for a in axes
+ ]
+
+ nameTable = self.font.get("name")
+ if not nameTable: # this only happens for unit tests
+ nameTable = self.font["name"] = newTable("name")
+ nameTable.names = []
+
+ if "ElidedFallbackNameID" in self.stat_:
+ nameID = self.stat_["ElidedFallbackNameID"]
+ name = nameTable.getDebugName(nameID)
+ if not name:
+ raise FeatureLibError(
+ f"ElidedFallbackNameID {nameID} points "
+ "to a nameID that does not exist in the "
+ '"name" table',
+ None,
+ )
+ elif "ElidedFallbackName" in self.stat_:
+ nameID = self.stat_["ElidedFallbackName"]
+
+ otl.buildStatTable(
+ self.font,
+ designAxes,
+ locations=format4_locations,
+ elidedFallbackName=nameID,
+ )
def build_codepages_(self, pages):
pages2bits = {
- 1252: 0, 1250: 1, 1251: 2, 1253: 3, 1254: 4, 1255: 5, 1256: 6,
- 1257: 7, 1258: 8, 874: 16, 932: 17, 936: 18, 949: 19, 950: 20,
- 1361: 21, 869: 48, 866: 49, 865: 50, 864: 51, 863: 52, 862: 53,
- 861: 54, 860: 55, 857: 56, 855: 57, 852: 58, 775: 59, 737: 60,
- 708: 61, 850: 62, 437: 63,
+ 1252: 0,
+ 1250: 1,
+ 1251: 2,
+ 1253: 3,
+ 1254: 4,
+ 1255: 5,
+ 1256: 6,
+ 1257: 7,
+ 1258: 8,
+ 874: 16,
+ 932: 17,
+ 936: 18,
+ 949: 19,
+ 950: 20,
+ 1361: 21,
+ 869: 48,
+ 866: 49,
+ 865: 50,
+ 864: 51,
+ 863: 52,
+ 862: 53,
+ 861: 54,
+ 860: 55,
+ 857: 56,
+ 855: 57,
+ 852: 58,
+ 775: 59,
+ 737: 60,
+ 708: 61,
+ 850: 62,
+ 437: 63,
}
bits = [pages2bits[p] for p in pages if p in pages2bits]
pages = []
@@ -472,16 +738,22 @@ class Builder(object):
def buildGDEF(self):
gdef = otTables.GDEF()
gdef.GlyphClassDef = self.buildGDEFGlyphClassDef_()
- gdef.AttachList = \
- otl.buildAttachList(self.attachPoints_, self.glyphMap)
- gdef.LigCaretList = \
- otl.buildLigCaretList(self.ligCaretCoords_, self.ligCaretPoints_,
- self.glyphMap)
+ gdef.AttachList = otl.buildAttachList(self.attachPoints_, self.glyphMap)
+ gdef.LigCaretList = otl.buildLigCaretList(
+ self.ligCaretCoords_, self.ligCaretPoints_, self.glyphMap
+ )
gdef.MarkAttachClassDef = self.buildGDEFMarkAttachClassDef_()
gdef.MarkGlyphSetsDef = self.buildGDEFMarkGlyphSetsDef_()
gdef.Version = 0x00010002 if gdef.MarkGlyphSetsDef else 0x00010000
- if any((gdef.GlyphClassDef, gdef.AttachList, gdef.LigCaretList,
- gdef.MarkAttachClassDef, gdef.MarkGlyphSetsDef)):
+ if any(
+ (
+ gdef.GlyphClassDef,
+ gdef.AttachList,
+ gdef.LigCaretList,
+ gdef.MarkAttachClassDef,
+ gdef.MarkGlyphSetsDef,
+ )
+ ):
result = newTable("GDEF")
result.table = gdef
return result
@@ -516,13 +788,20 @@ class Builder(object):
def buildGDEFMarkGlyphSetsDef_(self):
sets = []
- for glyphs, id_ in sorted(self.markFilterSets_.items(),
- key=lambda item: item[1]):
+ for glyphs, id_ in sorted(
+ self.markFilterSets_.items(), key=lambda item: item[1]
+ ):
sets.append(glyphs)
return otl.buildMarkGlyphSetsDef(sets, self.glyphMap)
+ def buildDebg(self):
+ if "Debg" not in self.font:
+ self.font["Debg"] = newTable("Debg")
+ self.font["Debg"].data = {}
+ self.font["Debg"].data[LOOKUP_DEBUG_INFO_KEY] = self.lookup_locations
+
def buildLookups_(self, tag):
- assert tag in ('GPOS', 'GSUB'), tag
+ assert tag in ("GPOS", "GSUB"), tag
for lookup in self.lookups_:
lookup.lookup_index = None
lookups = []
@@ -530,8 +809,17 @@ class Builder(object):
if lookup.table != tag:
continue
lookup.lookup_index = len(lookups)
+ self.lookup_locations[tag][str(lookup.lookup_index)] = LookupDebugInfo(
+ location=str(lookup.location),
+ name=self.get_lookup_name_(lookup),
+ feature=None,
+ )
lookups.append(lookup)
- return [l.build() for l in lookups]
+ try:
+ otLookups = [l.build() for l in lookups]
+ except OpenTypeLibError as e:
+ raise FeatureLibError(str(e), e.location) from e
+ return otLookups
def makeTable(self, tag):
table = getattr(otTables, tag, None)()
@@ -556,13 +844,25 @@ class Builder(object):
# l.lookup_index will be None when a lookup is not needed
# for the table under construction. For example, substitution
# rules will have no lookup_index while building GPOS tables.
- lookup_indices = tuple([l.lookup_index for l in lookups
- if l.lookup_index is not None])
+ lookup_indices = tuple(
+ [l.lookup_index for l in lookups if l.lookup_index is not None]
+ )
- size_feature = (tag == "GPOS" and feature_tag == "size")
+ size_feature = tag == "GPOS" and feature_tag == "size"
if len(lookup_indices) == 0 and not size_feature:
continue
+ for ix in lookup_indices:
+ try:
+ self.lookup_locations[tag][str(ix)] = self.lookup_locations[tag][
+ str(ix)
+ ]._replace(feature=key)
+ except KeyError:
+ warnings.warn(
+ "feaLib.Builder subclass needs upgrading to "
+ "stash debug information. See fonttools#2065."
+ )
+
feature_key = (feature_tag, lookup_indices)
feature_index = feature_indices.get(feature_key)
if feature_index is None:
@@ -570,14 +870,12 @@ class Builder(object):
frec = otTables.FeatureRecord()
frec.FeatureTag = feature_tag
frec.Feature = otTables.Feature()
- frec.Feature.FeatureParams = self.buildFeatureParams(
- feature_tag)
+ frec.Feature.FeatureParams = self.buildFeatureParams(feature_tag)
frec.Feature.LookupListIndex = list(lookup_indices)
frec.Feature.LookupCount = len(lookup_indices)
table.FeatureList.FeatureRecord.append(frec)
feature_indices[feature_key] = feature_index
- scripts.setdefault(script, {}).setdefault(lang, []).append(
- feature_index)
+ scripts.setdefault(script, {}).setdefault(lang, []).append(feature_index)
if self.required_features_.get((script, lang)) == feature_tag:
required_feature_indices[(script, lang)] = feature_index
@@ -593,17 +891,16 @@ class Builder(object):
langrec.LangSys = otTables.LangSys()
langrec.LangSys.LookupOrder = None
- req_feature_index = \
- required_feature_indices.get((script, lang))
+ req_feature_index = required_feature_indices.get((script, lang))
if req_feature_index is None:
langrec.LangSys.ReqFeatureIndex = 0xFFFF
else:
langrec.LangSys.ReqFeatureIndex = req_feature_index
- langrec.LangSys.FeatureIndex = [i for i in feature_indices
- if i != req_feature_index]
- langrec.LangSys.FeatureCount = \
- len(langrec.LangSys.FeatureIndex)
+ langrec.LangSys.FeatureIndex = [
+ i for i in feature_indices if i != req_feature_index
+ ]
+ langrec.LangSys.FeatureCount = len(langrec.LangSys.FeatureIndex)
if lang == "dflt":
srec.Script.DefaultLangSys = langrec.LangSys
@@ -618,26 +915,35 @@ class Builder(object):
table.LookupList.LookupCount = len(table.LookupList.Lookup)
return table
+ def get_lookup_name_(self, lookup):
+ rev = {v: k for k, v in self.named_lookups_.items()}
+ if lookup in rev:
+ return rev[lookup]
+ return None
+
def add_language_system(self, location, script, language):
# OpenType Feature File Specification, section 4.b.i
- if (script == "DFLT" and language == "dflt" and
- self.default_language_systems_):
+ if script == "DFLT" and language == "dflt" and self.default_language_systems_:
raise FeatureLibError(
'If "languagesystem DFLT dflt" is present, it must be '
- 'the first of the languagesystem statements', location)
+ "the first of the languagesystem statements",
+ location,
+ )
if script == "DFLT":
if self.seen_non_DFLT_script_:
raise FeatureLibError(
'languagesystems using the "DFLT" script tag must '
"precede all other languagesystems",
- location
+ location,
)
else:
self.seen_non_DFLT_script_ = True
if (script, language) in self.default_language_systems_:
raise FeatureLibError(
- '"languagesystem %s %s" has already been specified' %
- (script.strip(), language.strip()), location)
+ '"languagesystem %s %s" has already been specified'
+ % (script.strip(), language.strip()),
+ location,
+ )
self.default_language_systems_.add((script, language))
def get_default_language_systems_(self):
@@ -649,11 +955,11 @@ class Builder(object):
if self.default_language_systems_:
return frozenset(self.default_language_systems_)
else:
- return frozenset({('DFLT', 'dflt')})
+ return frozenset({("DFLT", "dflt")})
def start_feature(self, location, name):
self.language_systems = self.get_default_language_systems_()
- self.script_ = 'DFLT'
+ self.script_ = "DFLT"
self.cur_lookup_ = None
self.cur_feature_name_ = name
self.lookupflag_ = 0
@@ -672,24 +978,28 @@ class Builder(object):
def start_lookup_block(self, location, name):
if name in self.named_lookups_:
raise FeatureLibError(
- 'Lookup "%s" has already been defined' % name, location)
+ 'Lookup "%s" has already been defined' % name, location
+ )
if self.cur_feature_name_ == "aalt":
raise FeatureLibError(
"Lookup blocks cannot be placed inside 'aalt' features; "
"move it out, and then refer to it with a lookup statement",
- location)
+ location,
+ )
self.cur_lookup_name_ = name
self.named_lookups_[name] = None
self.cur_lookup_ = None
- self.lookupflag_ = 0
- self.lookupflag_markFilterSet_ = None
+ if self.cur_feature_name_ is None:
+ self.lookupflag_ = 0
+ self.lookupflag_markFilterSet_ = None
def end_lookup_block(self):
assert self.cur_lookup_name_ is not None
self.cur_lookup_name_ = None
self.cur_lookup_ = None
- self.lookupflag_ = 0
- self.lookupflag_markFilterSet_ = None
+ if self.cur_feature_name_ is None:
+ self.lookupflag_ = 0
+ self.lookupflag_markFilterSet_ = None
def add_lookup_call(self, lookup_name):
assert lookup_name in self.named_lookups_, lookup_name
@@ -701,16 +1011,24 @@ class Builder(object):
self.fontRevision_ = revision
def set_language(self, location, language, include_default, required):
- assert(len(language) == 4)
- if self.cur_feature_name_ in ('aalt', 'size'):
+ assert len(language) == 4
+ if self.cur_feature_name_ in ("aalt", "size"):
+ raise FeatureLibError(
+ "Language statements are not allowed "
+ 'within "feature %s"' % self.cur_feature_name_,
+ location,
+ )
+ if self.cur_feature_name_ is None:
raise FeatureLibError(
"Language statements are not allowed "
- "within \"feature %s\"" % self.cur_feature_name_, location)
+ "within standalone lookup blocks",
+ location,
+ )
self.cur_lookup_ = None
key = (self.script_, language, self.cur_feature_name_)
- lookups = self.features_.get((key[0], 'dflt', key[2]))
- if (language == 'dflt' or include_default) and lookups:
+ lookups = self.features_.get((key[0], "dflt", key[2]))
+ if (language == "dflt" or include_default) and lookups:
self.features_[key] = lookups[:]
else:
self.features_[key] = []
@@ -721,10 +1039,14 @@ class Builder(object):
if key in self.required_features_:
raise FeatureLibError(
"Language %s (script %s) has already "
- "specified feature %s as its required feature" % (
- language.strip(), self.script_.strip(),
- self.required_features_[key].strip()),
- location)
+ "specified feature %s as its required feature"
+ % (
+ language.strip(),
+ self.script_.strip(),
+ self.required_features_[key].strip(),
+ ),
+ location,
+ )
self.required_features_[key] = self.cur_feature_name_
def getMarkAttachClass_(self, location, glyphs):
@@ -739,9 +1061,9 @@ class Builder(object):
_, loc = self.markAttach_[glyph]
raise FeatureLibError(
"Glyph %s already has been assigned "
- "a MarkAttachmentType at %s:%d:%d" % (
- glyph, loc[0], loc[1], loc[2]),
- location)
+ "a MarkAttachmentType at %s" % (glyph, loc),
+ location,
+ )
self.markAttach_[glyph] = (id_, location)
return id_
@@ -768,16 +1090,25 @@ class Builder(object):
self.lookupflag_ = value
def set_script(self, location, script):
- if self.cur_feature_name_ in ('aalt', 'size'):
+ if self.cur_feature_name_ in ("aalt", "size"):
raise FeatureLibError(
"Script statements are not allowed "
- "within \"feature %s\"" % self.cur_feature_name_, location)
+ 'within "feature %s"' % self.cur_feature_name_,
+ location,
+ )
+ if self.cur_feature_name_ is None:
+ raise FeatureLibError(
+ "Script statements are not allowed " "within standalone lookup blocks",
+ location,
+ )
+ if self.language_systems == {(script, "dflt")}:
+ # Nothing to do.
+ return
self.cur_lookup_ = None
self.script_ = script
self.lookupflag_ = 0
self.lookupflag_markFilterSet_ = None
- self.set_language(location, "dflt",
- include_default=True, required=False)
+ self.set_language(location, "dflt", include_default=True, required=False)
def find_lookup_builders_(self, lookups):
"""Helper for building chain contextual substitutions
@@ -786,9 +1117,11 @@ class Builder(object):
If an input name is None, it gets mapped to a None LookupBuilder.
"""
lookup_builders = []
- for lookup in lookups:
- if lookup is not None:
- lookup_builders.append(self.named_lookups_.get(lookup.name))
+ for lookuplist in lookups:
+ if lookuplist is not None:
+ lookup_builders.append(
+ [self.named_lookups_.get(l.name) for l in lookuplist]
+ )
else:
lookup_builders.append(None)
return lookup_builders
@@ -799,17 +1132,21 @@ class Builder(object):
def add_chain_context_pos(self, location, prefix, glyphs, suffix, lookups):
lookup = self.get_lookup_(location, ChainContextPosBuilder)
- lookup.rules.append((prefix, glyphs, suffix,
- self.find_lookup_builders_(lookups)))
+ lookup.rules.append(
+ ChainContextualRule(
+ prefix, glyphs, suffix, self.find_lookup_builders_(lookups)
+ )
+ )
- def add_chain_context_subst(self, location,
- prefix, glyphs, suffix, lookups):
+ def add_chain_context_subst(self, location, prefix, glyphs, suffix, lookups):
lookup = self.get_lookup_(location, ChainContextSubstBuilder)
- lookup.substitutions.append((prefix, glyphs, suffix,
- self.find_lookup_builders_(lookups)))
+ lookup.rules.append(
+ ChainContextualRule(
+ prefix, glyphs, suffix, self.find_lookup_builders_(lookups)
+ )
+ )
- def add_alternate_subst(self, location,
- prefix, glyph, suffix, replacement):
+ def add_alternate_subst(self, location, prefix, glyph, suffix, replacement):
if self.cur_feature_name_ == "aalt":
alts = self.aalt_alternates_.setdefault(glyph, set())
alts.update(replacement)
@@ -817,20 +1154,20 @@ class Builder(object):
if prefix or suffix:
chain = self.get_lookup_(location, ChainContextSubstBuilder)
lookup = self.get_chained_lookup_(location, AlternateSubstBuilder)
- chain.substitutions.append((prefix, [glyph], suffix, [lookup]))
+ chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [lookup]))
else:
lookup = self.get_lookup_(location, AlternateSubstBuilder)
if glyph in lookup.alternates:
raise FeatureLibError(
- 'Already defined alternates for glyph "%s"' % glyph,
- location)
+ 'Already defined alternates for glyph "%s"' % glyph, location
+ )
lookup.alternates[glyph] = replacement
def add_feature_reference(self, location, featureName):
if self.cur_feature_name_ != "aalt":
raise FeatureLibError(
- 'Feature references are only allowed inside "feature aalt"',
- location)
+ 'Feature references are only allowed inside "feature aalt"', location
+ )
self.aalt_features_.append((location, featureName))
def add_featureName(self, tag):
@@ -840,7 +1177,7 @@ class Builder(object):
self.cv_parameters_.add(tag)
def add_to_cv_num_named_params(self, tag):
- """Adds new items to self.cv_num_named_params_
+ """Adds new items to ``self.cv_num_named_params_``
or increments the count of existing items."""
if tag in self.cv_num_named_params_:
self.cv_num_named_params_[tag] += 1
@@ -856,23 +1193,27 @@ class Builder(object):
else:
self.base_horiz_axis_ = (bases, scripts)
- def set_size_parameters(self, location, DesignSize, SubfamilyID,
- RangeStart, RangeEnd):
- if self.cur_feature_name_ != 'size':
+ def set_size_parameters(
+ self, location, DesignSize, SubfamilyID, RangeStart, RangeEnd
+ ):
+ if self.cur_feature_name_ != "size":
raise FeatureLibError(
"Parameters statements are not allowed "
- "within \"feature %s\"" % self.cur_feature_name_, location)
+ 'within "feature %s"' % self.cur_feature_name_,
+ location,
+ )
self.size_parameters_ = [DesignSize, SubfamilyID, RangeStart, RangeEnd]
for script, lang in self.language_systems:
key = (script, lang, self.cur_feature_name_)
self.features_.setdefault(key, [])
- def add_ligature_subst(self, location,
- prefix, glyphs, suffix, replacement, forceChain):
+ def add_ligature_subst(
+ self, location, prefix, glyphs, suffix, replacement, forceChain
+ ):
if prefix or suffix or forceChain:
chain = self.get_lookup_(location, ChainContextSubstBuilder)
lookup = self.get_chained_lookup_(location, LigatureSubstBuilder)
- chain.substitutions.append((prefix, glyphs, suffix, [lookup]))
+ chain.rules.append(ChainContextualRule(prefix, glyphs, suffix, [lookup]))
else:
lookup = self.get_lookup_(location, LigatureSubstBuilder)
@@ -884,25 +1225,34 @@ class Builder(object):
for g in sorted(itertools.product(*glyphs)):
lookup.ligatures[g] = replacement
- def add_multiple_subst(self, location,
- prefix, glyph, suffix, replacements, forceChain=False):
+ def add_multiple_subst(
+ self, location, prefix, glyph, suffix, replacements, forceChain=False
+ ):
if prefix or suffix or forceChain:
chain = self.get_lookup_(location, ChainContextSubstBuilder)
sub = self.get_chained_lookup_(location, MultipleSubstBuilder)
sub.mapping[glyph] = replacements
- chain.substitutions.append((prefix, [{glyph}], suffix, [sub]))
+ chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [sub]))
return
lookup = self.get_lookup_(location, MultipleSubstBuilder)
if glyph in lookup.mapping:
- raise FeatureLibError(
- 'Already defined substitution for glyph "%s"' % glyph,
- location)
+ if replacements == lookup.mapping[glyph]:
+ log.info(
+ "Removing duplicate multiple substitution from glyph"
+ ' "%s" to %s%s',
+ glyph,
+ replacements,
+ f" at {location}" if location else "",
+ )
+ else:
+ raise FeatureLibError(
+ 'Already defined substitution for glyph "%s"' % glyph, location
+ )
lookup.mapping[glyph] = replacements
- def add_reverse_chain_single_subst(self, location, old_prefix,
- old_suffix, mapping):
+ def add_reverse_chain_single_subst(self, location, old_prefix, old_suffix, mapping):
lookup = self.get_lookup_(location, ReverseChainSingleSubstBuilder)
- lookup.substitutions.append((old_prefix, old_suffix, mapping))
+ lookup.rules.append((old_prefix, old_suffix, mapping))
def add_single_subst(self, location, prefix, suffix, mapping, forceChain):
if self.cur_feature_name_ == "aalt":
@@ -916,10 +1266,20 @@ class Builder(object):
lookup = self.get_lookup_(location, SingleSubstBuilder)
for (from_glyph, to_glyph) in mapping.items():
if from_glyph in lookup.mapping:
- raise FeatureLibError(
- 'Already defined rule for replacing glyph "%s" by "%s"' %
- (from_glyph, lookup.mapping[from_glyph]),
- location)
+ if to_glyph == lookup.mapping[from_glyph]:
+ log.info(
+ "Removing duplicate single substitution from glyph"
+ ' "%s" to "%s" at %s',
+ from_glyph,
+ to_glyph,
+ location,
+ )
+ else:
+ raise FeatureLibError(
+ 'Already defined rule for replacing glyph "%s" by "%s"'
+ % (from_glyph, lookup.mapping[from_glyph]),
+ location,
+ )
lookup.mapping[from_glyph] = to_glyph
def add_single_subst_chained_(self, location, prefix, suffix, mapping):
@@ -929,14 +1289,18 @@ class Builder(object):
if sub is None:
sub = self.get_chained_lookup_(location, SingleSubstBuilder)
sub.mapping.update(mapping)
- chain.substitutions.append((prefix, [mapping.keys()], suffix, [sub]))
+ chain.rules.append(
+ ChainContextualRule(prefix, [list(mapping.keys())], suffix, [sub])
+ )
def add_cursive_pos(self, location, glyphclass, entryAnchor, exitAnchor):
lookup = self.get_lookup_(location, CursivePosBuilder)
lookup.add_attachment(
- location, glyphclass,
+ location,
+ glyphclass,
makeOpenTypeAnchor(entryAnchor),
- makeOpenTypeAnchor(exitAnchor))
+ makeOpenTypeAnchor(exitAnchor),
+ )
def add_marks_(self, location, lookupBuilder, marks):
"""Helper for add_mark_{base,liga,mark}_pos."""
@@ -945,15 +1309,15 @@ class Builder(object):
for mark in markClassDef.glyphs.glyphSet():
if mark not in lookupBuilder.marks:
otMarkAnchor = makeOpenTypeAnchor(markClassDef.anchor)
- lookupBuilder.marks[mark] = (
- markClass.name, otMarkAnchor)
+ lookupBuilder.marks[mark] = (markClass.name, otMarkAnchor)
else:
existingMarkClass = lookupBuilder.marks[mark][0]
if markClass.name != existingMarkClass:
raise FeatureLibError(
- "Glyph %s cannot be in both @%s and @%s" % (
- mark, existingMarkClass, markClass.name),
- location)
+ "Glyph %s cannot be in both @%s and @%s"
+ % (mark, existingMarkClass, markClass.name),
+ location,
+ )
def add_mark_base_pos(self, location, bases, marks):
builder = self.get_lookup_(location, MarkBasePosBuilder)
@@ -961,8 +1325,7 @@ class Builder(object):
for baseAnchor, markClass in marks:
otBaseAnchor = makeOpenTypeAnchor(baseAnchor)
for base in bases:
- builder.bases.setdefault(base, {})[markClass.name] = (
- otBaseAnchor)
+ builder.bases.setdefault(base, {})[markClass.name] = otBaseAnchor
def add_mark_lig_pos(self, location, ligatures, components):
builder = self.get_lookup_(location, MarkLigPosBuilder)
@@ -982,20 +1345,24 @@ class Builder(object):
for baseAnchor, markClass in marks:
otBaseAnchor = makeOpenTypeAnchor(baseAnchor)
for baseMark in baseMarks:
- builder.baseMarks.setdefault(baseMark, {})[markClass.name] = (
- otBaseAnchor)
+ builder.baseMarks.setdefault(baseMark, {})[
+ markClass.name
+ ] = otBaseAnchor
- def add_class_pair_pos(self, location, glyphclass1, value1,
- glyphclass2, value2):
+ def add_class_pair_pos(self, location, glyphclass1, value1, glyphclass2, value2):
lookup = self.get_lookup_(location, PairPosBuilder)
- lookup.addClassPair(location, glyphclass1, value1, glyphclass2, value2)
+ v1 = makeOpenTypeValueRecord(value1, pairPosContext=True)
+ v2 = makeOpenTypeValueRecord(value2, pairPosContext=True)
+ lookup.addClassPair(location, glyphclass1, v1, glyphclass2, v2)
def add_subtable_break(self, location):
self.cur_lookup_.add_subtable_break(location)
def add_specific_pair_pos(self, location, glyph1, value1, glyph2, value2):
lookup = self.get_lookup_(location, PairPosBuilder)
- lookup.addGlyphPair(location, glyph1, value1, glyph2, value2)
+ v1 = makeOpenTypeValueRecord(value1, pairPosContext=True)
+ v2 = makeOpenTypeValueRecord(value2, pairPosContext=True)
+ lookup.addGlyphPair(location, glyph1, v1, glyph2, v2)
def add_single_pos(self, location, prefix, suffix, pos, forceChain):
if prefix or suffix or forceChain:
@@ -1003,8 +1370,12 @@ class Builder(object):
else:
lookup = self.get_lookup_(location, SinglePosBuilder)
for glyphs, value in pos:
+ otValueRecord = makeOpenTypeValueRecord(value, pairPosContext=False)
for glyph in glyphs:
- lookup.add_pos(location, glyph, value)
+ try:
+ lookup.add_pos(location, glyph, otValueRecord)
+ except OpenTypeLibError as e:
+ raise FeatureLibError(str(e), e.location) from e
def add_single_pos_chained_(self, location, prefix, suffix, pos):
# https://github.com/fonttools/fonttools/issues/514
@@ -1017,29 +1388,32 @@ class Builder(object):
if value is None:
subs.append(None)
continue
- otValue, _ = makeOpenTypeValueRecord(value, pairPosContext=False)
+ otValue = makeOpenTypeValueRecord(value, pairPosContext=False)
sub = chain.find_chainable_single_pos(targets, glyphs, otValue)
if sub is None:
sub = self.get_chained_lookup_(location, SinglePosBuilder)
targets.append(sub)
for glyph in glyphs:
- sub.add_pos(location, glyph, value)
+ sub.add_pos(location, glyph, otValue)
subs.append(sub)
assert len(pos) == len(subs), (pos, subs)
chain.rules.append(
- (prefix, [g for g, v in pos], suffix, subs))
+ ChainContextualRule(prefix, [g for g, v in pos], suffix, subs)
+ )
def setGlyphClass_(self, location, glyph, glyphClass):
oldClass, oldLocation = self.glyphClassDefs_.get(glyph, (None, None))
if oldClass and oldClass != glyphClass:
raise FeatureLibError(
- "Glyph %s was assigned to a different class at %s:%s:%s" %
- (glyph, oldLocation[0], oldLocation[1], oldLocation[2]),
- location)
+ "Glyph %s was assigned to a different class at %s"
+ % (glyph, oldLocation),
+ location,
+ )
self.glyphClassDefs_[glyph] = (glyphClass, location)
- def add_glyphClassDef(self, location, baseGlyphs, ligatureGlyphs,
- markGlyphs, componentGlyphs):
+ def add_glyphClassDef(
+ self, location, baseGlyphs, ligatureGlyphs, markGlyphs, componentGlyphs
+ ):
for glyph in baseGlyphs:
self.setGlyphClass_(location, glyph, 1)
for glyph in ligatureGlyphs:
@@ -1051,14 +1425,15 @@ class Builder(object):
def add_ligatureCaretByIndex_(self, location, glyphs, carets):
for glyph in glyphs:
- self.ligCaretPoints_.setdefault(glyph, set()).update(carets)
+ if glyph not in self.ligCaretPoints_:
+ self.ligCaretPoints_[glyph] = carets
def add_ligatureCaretByPos_(self, location, glyphs, carets):
for glyph in glyphs:
- self.ligCaretCoords_.setdefault(glyph, set()).update(carets)
+ if glyph not in self.ligCaretCoords_:
+ self.ligCaretCoords_[glyph] = carets
- def add_name_record(self, location, nameID, platformID, platEncID,
- langID, string):
+ def add_name_record(self, location, nameID, platformID, platEncID, langID, string):
self.names_.append([nameID, platformID, platEncID, langID, string])
def add_os2_field(self, key, value):
@@ -1080,8 +1455,7 @@ def makeOpenTypeAnchor(anchor):
deviceX = otl.buildDevice(dict(anchor.xDeviceTable))
if anchor.yDeviceTable is not None:
deviceY = otl.buildDevice(dict(anchor.yDeviceTable))
- return otl.buildAnchor(anchor.x, anchor.y, anchor.contourpoint,
- deviceX, deviceY)
+ return otl.buildAnchor(anchor.x, anchor.y, anchor.contourpoint, deviceX, deviceY)
_VALUEREC_ATTRS = {
@@ -1092,9 +1466,9 @@ _VALUEREC_ATTRS = {
def makeOpenTypeValueRecord(v, pairPosContext):
- """ast.ValueRecord --> (otBase.ValueRecord, int ValueFormat)"""
+ """ast.ValueRecord --> otBase.ValueRecord"""
if not v:
- return None, 0
+ return None
vr = {}
for astName, (otName, isDevice) in _VALUEREC_ATTRS.items():
@@ -1104,547 +1478,4 @@ def makeOpenTypeValueRecord(v, pairPosContext):
if pairPosContext and not vr:
vr = {"YAdvance": 0} if v.vertical else {"XAdvance": 0}
valRec = otl.buildValue(vr)
- return valRec, valRec.getFormat()
-
-
-class LookupBuilder(object):
- SUBTABLE_BREAK_ = "SUBTABLE_BREAK"
-
- def __init__(self, font, location, table, lookup_type):
- self.font = font
- self.glyphMap = font.getReverseGlyphMap()
- self.location = location
- self.table, self.lookup_type = table, lookup_type
- self.lookupflag = 0
- self.markFilterSet = None
- self.lookup_index = None # assigned when making final tables
- assert table in ('GPOS', 'GSUB')
-
- def equals(self, other):
- return (isinstance(other, self.__class__) and
- self.table == other.table and
- self.lookupflag == other.lookupflag and
- self.markFilterSet == other.markFilterSet)
-
- def inferGlyphClasses(self):
- """Infers glyph glasses for the GDEF table, such as {"cedilla":3}."""
- return {}
-
- def getAlternateGlyphs(self):
- """Helper for building 'aalt' features."""
- return {}
-
- def buildLookup_(self, subtables):
- return otl.buildLookup(subtables, self.lookupflag, self.markFilterSet)
-
- def buildMarkClasses_(self, marks):
- """{"cedilla": ("BOTTOM", ast.Anchor), ...} --> {"BOTTOM":0, "TOP":1}
-
- Helper for MarkBasePostBuilder, MarkLigPosBuilder, and
- MarkMarkPosBuilder. Seems to return the same numeric IDs
- for mark classes as the AFDKO makeotf tool.
- """
- ids = {}
- for mark in sorted(marks.keys(), key=self.font.getGlyphID):
- markClassName, _markAnchor = marks[mark]
- if markClassName not in ids:
- ids[markClassName] = len(ids)
- return ids
-
- def setBacktrackCoverage_(self, prefix, subtable):
- subtable.BacktrackGlyphCount = len(prefix)
- subtable.BacktrackCoverage = []
- for p in reversed(prefix):
- coverage = otl.buildCoverage(p, self.glyphMap)
- subtable.BacktrackCoverage.append(coverage)
-
- def setLookAheadCoverage_(self, suffix, subtable):
- subtable.LookAheadGlyphCount = len(suffix)
- subtable.LookAheadCoverage = []
- for s in suffix:
- coverage = otl.buildCoverage(s, self.glyphMap)
- subtable.LookAheadCoverage.append(coverage)
-
- def setInputCoverage_(self, glyphs, subtable):
- subtable.InputGlyphCount = len(glyphs)
- subtable.InputCoverage = []
- for g in glyphs:
- coverage = otl.buildCoverage(g, self.glyphMap)
- subtable.InputCoverage.append(coverage)
-
- def build_subst_subtables(self, mapping, klass):
- substitutions = [{}]
- for key in mapping:
- if key[0] == self.SUBTABLE_BREAK_:
- substitutions.append({})
- else:
- substitutions[-1][key] = mapping[key]
- subtables = [klass(s) for s in substitutions]
- return subtables
-
- def add_subtable_break(self, location):
- log.warning(FeatureLibError(
- 'unsupported "subtable" statement for lookup type',
- location
- ))
-
-
-class AlternateSubstBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GSUB', 3)
- self.alternates = OrderedDict()
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.alternates == other.alternates)
-
- def build(self):
- subtables = self.build_subst_subtables(self.alternates,
- otl.buildAlternateSubstSubtable)
- return self.buildLookup_(subtables)
-
- def getAlternateGlyphs(self):
- return self.alternates
-
- def add_subtable_break(self, location):
- self.alternates[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
-
-
-class ChainContextPosBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GPOS', 8)
- self.rules = [] # (prefix, input, suffix, lookups)
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.rules == other.rules)
-
- def build(self):
- subtables = []
- for (prefix, glyphs, suffix, lookups) in self.rules:
- if prefix == self.SUBTABLE_BREAK_:
- continue
- st = otTables.ChainContextPos()
- subtables.append(st)
- st.Format = 3
- self.setBacktrackCoverage_(prefix, st)
- self.setLookAheadCoverage_(suffix, st)
- self.setInputCoverage_(glyphs, st)
-
- st.PosCount = len([l for l in lookups if l is not None])
- st.PosLookupRecord = []
- for sequenceIndex, l in enumerate(lookups):
- if l is not None:
- rec = otTables.PosLookupRecord()
- rec.SequenceIndex = sequenceIndex
- rec.LookupListIndex = l.lookup_index
- st.PosLookupRecord.append(rec)
- return self.buildLookup_(subtables)
-
- def find_chainable_single_pos(self, lookups, glyphs, value):
- """Helper for add_single_pos_chained_()"""
- res = None
- for lookup in lookups[::-1]:
- if lookup == self.SUBTABLE_BREAK_:
- return res
- if isinstance(lookup, SinglePosBuilder) and \
- all(lookup.can_add(glyph, value) for glyph in glyphs):
- res = lookup
- return res
-
- def add_subtable_break(self, location):
- self.rules.append((self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_,
- self.SUBTABLE_BREAK_, [self.SUBTABLE_BREAK_]))
-
-
-class ChainContextSubstBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GSUB', 6)
- self.substitutions = [] # (prefix, input, suffix, lookups)
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.substitutions == other.substitutions)
-
- def build(self):
- subtables = []
- for (prefix, input, suffix, lookups) in self.substitutions:
- if prefix == self.SUBTABLE_BREAK_:
- continue
- st = otTables.ChainContextSubst()
- subtables.append(st)
- st.Format = 3
- self.setBacktrackCoverage_(prefix, st)
- self.setLookAheadCoverage_(suffix, st)
- self.setInputCoverage_(input, st)
-
- st.SubstCount = len([l for l in lookups if l is not None])
- st.SubstLookupRecord = []
- for sequenceIndex, l in enumerate(lookups):
- if l is not None:
- rec = otTables.SubstLookupRecord()
- rec.SequenceIndex = sequenceIndex
- rec.LookupListIndex = l.lookup_index
- st.SubstLookupRecord.append(rec)
- return self.buildLookup_(subtables)
-
- def getAlternateGlyphs(self):
- result = {}
- for (_, _, _, lookups) in self.substitutions:
- if lookups == self.SUBTABLE_BREAK_:
- continue
- for lookup in lookups:
- alts = lookup.getAlternateGlyphs()
- for glyph, replacements in alts.items():
- result.setdefault(glyph, set()).update(replacements)
- return result
-
- def find_chainable_single_subst(self, glyphs):
- """Helper for add_single_subst_chained_()"""
- res = None
- for _, _, _, substitutions in self.substitutions[::-1]:
- if substitutions == self.SUBTABLE_BREAK_:
- return res
- for sub in substitutions:
- if (isinstance(sub, SingleSubstBuilder) and
- not any(g in glyphs for g in sub.mapping.keys())):
- res = sub
- return res
-
- def add_subtable_break(self, location):
- self.substitutions.append((self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_,
- self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_))
-
-
-class LigatureSubstBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GSUB', 4)
- self.ligatures = OrderedDict() # {('f','f','i'): 'f_f_i'}
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.ligatures == other.ligatures)
-
- def build(self):
- subtables = self.build_subst_subtables(self.ligatures,
- otl.buildLigatureSubstSubtable)
- return self.buildLookup_(subtables)
-
- def add_subtable_break(self, location):
- self.ligatures[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
-
-
-class MultipleSubstBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GSUB', 2)
- self.mapping = OrderedDict()
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.mapping == other.mapping)
-
- def build(self):
- subtables = self.build_subst_subtables(self.mapping,
- otl.buildMultipleSubstSubtable)
- return self.buildLookup_(subtables)
-
- def add_subtable_break(self, location):
- self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
-
-
-class CursivePosBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GPOS', 3)
- self.attachments = {}
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.attachments == other.attachments)
-
- def add_attachment(self, location, glyphs, entryAnchor, exitAnchor):
- for glyph in glyphs:
- self.attachments[glyph] = (entryAnchor, exitAnchor)
-
- def build(self):
- st = otl.buildCursivePosSubtable(self.attachments, self.glyphMap)
- return self.buildLookup_([st])
-
-
-class MarkBasePosBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GPOS', 4)
- self.marks = {} # glyphName -> (markClassName, anchor)
- self.bases = {} # glyphName -> {markClassName: anchor}
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.marks == other.marks and
- self.bases == other.bases)
-
- def inferGlyphClasses(self):
- result = {glyph: 1 for glyph in self.bases}
- result.update({glyph: 3 for glyph in self.marks})
- return result
-
- def build(self):
- markClasses = self.buildMarkClasses_(self.marks)
- marks = {mark: (markClasses[mc], anchor)
- for mark, (mc, anchor) in self.marks.items()}
- bases = {}
- for glyph, anchors in self.bases.items():
- bases[glyph] = {markClasses[mc]: anchor
- for (mc, anchor) in anchors.items()}
- subtables = otl.buildMarkBasePos(marks, bases, self.glyphMap)
- return self.buildLookup_(subtables)
-
-
-class MarkLigPosBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GPOS', 5)
- self.marks = {} # glyphName -> (markClassName, anchor)
- self.ligatures = {} # glyphName -> [{markClassName: anchor}, ...]
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.marks == other.marks and
- self.ligatures == other.ligatures)
-
- def inferGlyphClasses(self):
- result = {glyph: 2 for glyph in self.ligatures}
- result.update({glyph: 3 for glyph in self.marks})
- return result
-
- def build(self):
- markClasses = self.buildMarkClasses_(self.marks)
- marks = {mark: (markClasses[mc], anchor)
- for mark, (mc, anchor) in self.marks.items()}
- ligs = {}
- for lig, components in self.ligatures.items():
- ligs[lig] = []
- for c in components:
- ligs[lig].append({markClasses[mc]: a for mc, a in c.items()})
- subtables = otl.buildMarkLigPos(marks, ligs, self.glyphMap)
- return self.buildLookup_(subtables)
-
-
-class MarkMarkPosBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GPOS', 6)
- self.marks = {} # glyphName -> (markClassName, anchor)
- self.baseMarks = {} # glyphName -> {markClassName: anchor}
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.marks == other.marks and
- self.baseMarks == other.baseMarks)
-
- def inferGlyphClasses(self):
- result = {glyph: 3 for glyph in self.baseMarks}
- result.update({glyph: 3 for glyph in self.marks})
- return result
-
- def build(self):
- markClasses = self.buildMarkClasses_(self.marks)
- markClassList = sorted(markClasses.keys(), key=markClasses.get)
- marks = {mark: (markClasses[mc], anchor)
- for mark, (mc, anchor) in self.marks.items()}
-
- st = otTables.MarkMarkPos()
- st.Format = 1
- st.ClassCount = len(markClasses)
- st.Mark1Coverage = otl.buildCoverage(marks, self.glyphMap)
- st.Mark2Coverage = otl.buildCoverage(self.baseMarks, self.glyphMap)
- st.Mark1Array = otl.buildMarkArray(marks, self.glyphMap)
- st.Mark2Array = otTables.Mark2Array()
- st.Mark2Array.Mark2Count = len(st.Mark2Coverage.glyphs)
- st.Mark2Array.Mark2Record = []
- for base in st.Mark2Coverage.glyphs:
- anchors = [self.baseMarks[base].get(mc) for mc in markClassList]
- st.Mark2Array.Mark2Record.append(otl.buildMark2Record(anchors))
- return self.buildLookup_([st])
-
-
-class ReverseChainSingleSubstBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GSUB', 8)
- self.substitutions = [] # (prefix, suffix, mapping)
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.substitutions == other.substitutions)
-
- def build(self):
- subtables = []
- for prefix, suffix, mapping in self.substitutions:
- st = otTables.ReverseChainSingleSubst()
- st.Format = 1
- self.setBacktrackCoverage_(prefix, st)
- self.setLookAheadCoverage_(suffix, st)
- st.Coverage = otl.buildCoverage(mapping.keys(), self.glyphMap)
- st.GlyphCount = len(mapping)
- st.Substitute = [mapping[g] for g in st.Coverage.glyphs]
- subtables.append(st)
- return self.buildLookup_(subtables)
-
- def add_subtable_break(self, location):
- # Nothing to do here, each substitution is in its own subtable.
- pass
-
-
-class SingleSubstBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GSUB', 1)
- self.mapping = OrderedDict()
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.mapping == other.mapping)
-
- def build(self):
- subtables = self.build_subst_subtables(self.mapping,
- otl.buildSingleSubstSubtable)
- return self.buildLookup_(subtables)
-
- def getAlternateGlyphs(self):
- return {glyph: set([repl]) for glyph, repl in self.mapping.items()}
-
- def add_subtable_break(self, location):
- self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
-
-
-class ClassPairPosSubtableBuilder(object):
- def __init__(self, builder, valueFormat1, valueFormat2):
- self.builder_ = builder
- self.classDef1_, self.classDef2_ = None, None
- self.values_ = {} # (glyphclass1, glyphclass2) --> (value1, value2)
- self.valueFormat1_, self.valueFormat2_ = valueFormat1, valueFormat2
- self.forceSubtableBreak_ = False
- self.subtables_ = []
-
- def addPair(self, gc1, value1, gc2, value2):
- mergeable = (not self.forceSubtableBreak_ and
- self.classDef1_ is not None and
- self.classDef1_.canAdd(gc1) and
- self.classDef2_ is not None and
- self.classDef2_.canAdd(gc2))
- if not mergeable:
- self.flush_()
- self.classDef1_ = otl.ClassDefBuilder(useClass0=True)
- self.classDef2_ = otl.ClassDefBuilder(useClass0=False)
- self.values_ = {}
- self.classDef1_.add(gc1)
- self.classDef2_.add(gc2)
- self.values_[(gc1, gc2)] = (value1, value2)
-
- def addSubtableBreak(self):
- self.forceSubtableBreak_ = True
-
- def subtables(self):
- self.flush_()
- return self.subtables_
-
- def flush_(self):
- if self.classDef1_ is None or self.classDef2_ is None:
- return
- st = otl.buildPairPosClassesSubtable(self.values_,
- self.builder_.glyphMap)
- if st.Coverage is None:
- return
- self.subtables_.append(st)
- self.forceSubtableBreak_ = False
-
-
-class PairPosBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GPOS', 2)
- self.pairs = [] # [(gc1, value1, gc2, value2)*]
- self.glyphPairs = {} # (glyph1, glyph2) --> (value1, value2)
- self.locations = {} # (gc1, gc2) --> (filepath, line, column)
-
- def addClassPair(self, location, glyphclass1, value1, glyphclass2, value2):
- self.pairs.append((glyphclass1, value1, glyphclass2, value2))
-
- def addGlyphPair(self, location, glyph1, value1, glyph2, value2):
- key = (glyph1, glyph2)
- oldValue = self.glyphPairs.get(key, None)
- if oldValue is not None:
- # the Feature File spec explicitly allows specific pairs generated
- # by an 'enum' rule to be overridden by preceding single pairs
- otherLoc = self.locations[key]
- log.debug(
- 'Already defined position for pair %s %s at %s:%d:%d; '
- 'choosing the first value',
- glyph1, glyph2, otherLoc[0], otherLoc[1], otherLoc[2])
- else:
- val1, _ = makeOpenTypeValueRecord(value1, pairPosContext=True)
- val2, _ = makeOpenTypeValueRecord(value2, pairPosContext=True)
- self.glyphPairs[key] = (val1, val2)
- self.locations[key] = location
-
- def add_subtable_break(self, location):
- self.pairs.append((self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_,
- self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_))
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.glyphPairs == other.glyphPairs and
- self.pairs == other.pairs)
-
- def build(self):
- builders = {}
- builder = None
- for glyphclass1, value1, glyphclass2, value2 in self.pairs:
- if glyphclass1 is self.SUBTABLE_BREAK_:
- if builder is not None:
- builder.addSubtableBreak()
- continue
- val1, valFormat1 = makeOpenTypeValueRecord(
- value1, pairPosContext=True)
- val2, valFormat2 = makeOpenTypeValueRecord(
- value2, pairPosContext=True)
- builder = builders.get((valFormat1, valFormat2))
- if builder is None:
- builder = ClassPairPosSubtableBuilder(
- self, valFormat1, valFormat2)
- builders[(valFormat1, valFormat2)] = builder
- builder.addPair(glyphclass1, val1, glyphclass2, val2)
- subtables = []
- if self.glyphPairs:
- subtables.extend(
- otl.buildPairPosGlyphs(self.glyphPairs, self.glyphMap))
- for key in sorted(builders.keys()):
- subtables.extend(builders[key].subtables())
- return self.buildLookup_(subtables)
-
-
-class SinglePosBuilder(LookupBuilder):
- def __init__(self, font, location):
- LookupBuilder.__init__(self, font, location, 'GPOS', 1)
- self.locations = {} # glyph -> (filename, line, column)
- self.mapping = {} # glyph -> otTables.ValueRecord
-
- def add_pos(self, location, glyph, valueRecord):
- otValueRecord, _ = makeOpenTypeValueRecord(
- valueRecord, pairPosContext=False)
- if not self.can_add(glyph, otValueRecord):
- otherLoc = self.locations[glyph]
- raise FeatureLibError(
- 'Already defined different position for glyph "%s" at %s:%d:%d'
- % (glyph, otherLoc[0], otherLoc[1], otherLoc[2]),
- location)
- if otValueRecord:
- self.mapping[glyph] = otValueRecord
- self.locations[glyph] = location
-
- def can_add(self, glyph, value):
- assert isinstance(value, otl.ValueRecord)
- curValue = self.mapping.get(glyph)
- return curValue is None or curValue == value
-
- def equals(self, other):
- return (LookupBuilder.equals(self, other) and
- self.mapping == other.mapping)
-
- def build(self):
- subtables = otl.buildSinglePos(self.mapping, self.glyphMap)
- return self.buildLookup_(subtables)
+ return valRec
diff --git a/Lib/fontTools/feaLib/error.py b/Lib/fontTools/feaLib/error.py
index 8281f959..a2c5f9db 100644
--- a/Lib/fontTools/feaLib/error.py
+++ b/Lib/fontTools/feaLib/error.py
@@ -1,7 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-
-
class FeatureLibError(Exception):
def __init__(self, message, location):
Exception.__init__(self, message)
@@ -10,11 +6,17 @@ class FeatureLibError(Exception):
def __str__(self):
message = Exception.__str__(self)
if self.location:
- path, line, column = self.location
- return "%s:%d:%d: %s" % (path, line, column, message)
+ return f"{self.location}: {message}"
else:
return message
class IncludedFeaNotFound(FeatureLibError):
- pass
+ def __str__(self):
+ assert self.location is not None
+
+ message = (
+ "The following feature file should be included but cannot be found: "
+ f"{Exception.__str__(self)}"
+ )
+ return f"{self.location}: {message}"
diff --git a/Lib/fontTools/feaLib/lexer.py b/Lib/fontTools/feaLib/lexer.py
index 095cb668..140fbd82 100644
--- a/Lib/fontTools/feaLib/lexer.py
+++ b/Lib/fontTools/feaLib/lexer.py
@@ -1,13 +1,14 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
from fontTools.feaLib.error import FeatureLibError, IncludedFeaNotFound
+from fontTools.feaLib.location import FeatureLibLocation
import re
import os
class Lexer(object):
NUMBER = "NUMBER"
+ HEXADECIMAL = "HEXADECIMAL"
+ OCTAL = "OCTAL"
+ NUMBERS = (NUMBER, HEXADECIMAL, OCTAL)
FLOAT = "FLOAT"
STRING = "STRING"
NAME = "NAME"
@@ -56,7 +57,7 @@ class Lexer(object):
def location_(self):
column = self.pos_ - self.line_start_ + 1
- return (self.filename_ or "<features>", self.line_, column)
+ return FeatureLibLocation(self.filename_ or "<features>", self.line_, column)
def next_(self):
self.scan_over_(Lexer.CHAR_WHITESPACE_)
@@ -75,72 +76,75 @@ class Lexer(object):
self.line_start_ = self.pos_
return (Lexer.NEWLINE, None, location)
if cur_char == "\r":
- self.pos_ += (2 if next_char == "\n" else 1)
+ self.pos_ += 2 if next_char == "\n" else 1
self.line_ += 1
self.line_start_ = self.pos_
return (Lexer.NEWLINE, None, location)
if cur_char == "#":
self.scan_until_(Lexer.CHAR_NEWLINE_)
- return (Lexer.COMMENT, text[start:self.pos_], location)
+ return (Lexer.COMMENT, text[start : self.pos_], location)
if self.mode_ is Lexer.MODE_FILENAME_:
if cur_char != "(":
- raise FeatureLibError("Expected '(' before file name",
- location)
+ raise FeatureLibError("Expected '(' before file name", location)
self.scan_until_(")")
cur_char = text[self.pos_] if self.pos_ < limit else None
if cur_char != ")":
- raise FeatureLibError("Expected ')' after file name",
- location)
+ raise FeatureLibError("Expected ')' after file name", location)
self.pos_ += 1
self.mode_ = Lexer.MODE_NORMAL_
- return (Lexer.FILENAME, text[start + 1:self.pos_ - 1], location)
+ return (Lexer.FILENAME, text[start + 1 : self.pos_ - 1], location)
if cur_char == "\\" and next_char in Lexer.CHAR_DIGIT_:
self.pos_ += 1
self.scan_over_(Lexer.CHAR_DIGIT_)
- return (Lexer.CID, int(text[start + 1:self.pos_], 10), location)
+ return (Lexer.CID, int(text[start + 1 : self.pos_], 10), location)
if cur_char == "@":
self.pos_ += 1
self.scan_over_(Lexer.CHAR_NAME_CONTINUATION_)
- glyphclass = text[start + 1:self.pos_]
+ glyphclass = text[start + 1 : self.pos_]
if len(glyphclass) < 1:
raise FeatureLibError("Expected glyph class name", location)
if len(glyphclass) > 63:
raise FeatureLibError(
- "Glyph class names must not be longer than 63 characters",
- location)
+ "Glyph class names must not be longer than 63 characters", location
+ )
if not Lexer.RE_GLYPHCLASS.match(glyphclass):
raise FeatureLibError(
"Glyph class names must consist of letters, digits, "
- "underscore, period or hyphen", location)
+ "underscore, period or hyphen",
+ location,
+ )
return (Lexer.GLYPHCLASS, glyphclass, location)
if cur_char in Lexer.CHAR_NAME_START_:
self.pos_ += 1
self.scan_over_(Lexer.CHAR_NAME_CONTINUATION_)
- token = text[start:self.pos_]
+ token = text[start : self.pos_]
if token == "include":
self.mode_ = Lexer.MODE_FILENAME_
return (Lexer.NAME, token, location)
if cur_char == "0" and next_char in "xX":
self.pos_ += 2
self.scan_over_(Lexer.CHAR_HEXDIGIT_)
- return (Lexer.NUMBER, int(text[start:self.pos_], 16), location)
+ return (Lexer.HEXADECIMAL, int(text[start : self.pos_], 16), location)
+ if cur_char == "0" and next_char in Lexer.CHAR_DIGIT_:
+ self.scan_over_(Lexer.CHAR_DIGIT_)
+ return (Lexer.OCTAL, int(text[start : self.pos_], 8), location)
if cur_char in Lexer.CHAR_DIGIT_:
self.scan_over_(Lexer.CHAR_DIGIT_)
if self.pos_ >= limit or text[self.pos_] != ".":
- return (Lexer.NUMBER, int(text[start:self.pos_], 10), location)
+ return (Lexer.NUMBER, int(text[start : self.pos_], 10), location)
self.scan_over_(".")
self.scan_over_(Lexer.CHAR_DIGIT_)
- return (Lexer.FLOAT, float(text[start:self.pos_]), location)
+ return (Lexer.FLOAT, float(text[start : self.pos_]), location)
if cur_char == "-" and next_char in Lexer.CHAR_DIGIT_:
self.pos_ += 1
self.scan_over_(Lexer.CHAR_DIGIT_)
if self.pos_ >= limit or text[self.pos_] != ".":
- return (Lexer.NUMBER, int(text[start:self.pos_], 10), location)
+ return (Lexer.NUMBER, int(text[start : self.pos_], 10), location)
self.scan_over_(".")
self.scan_over_(Lexer.CHAR_DIGIT_)
- return (Lexer.FLOAT, float(text[start:self.pos_]), location)
+ return (Lexer.FLOAT, float(text[start : self.pos_]), location)
if cur_char in Lexer.CHAR_SYMBOL_:
self.pos_ += 1
return (Lexer.SYMBOL, cur_char, location)
@@ -150,13 +154,11 @@ class Lexer(object):
if self.pos_ < self.text_length_ and self.text_[self.pos_] == '"':
self.pos_ += 1
# strip newlines embedded within a string
- string = re.sub("[\r\n]", "", text[start + 1:self.pos_ - 1])
+ string = re.sub("[\r\n]", "", text[start + 1 : self.pos_ - 1])
return (Lexer.STRING, string, location)
else:
- raise FeatureLibError("Expected '\"' to terminate string",
- location)
- raise FeatureLibError("Unexpected character: %r" % cur_char,
- location)
+ raise FeatureLibError("Expected '\"' to terminate string", location)
+ raise FeatureLibError("Unexpected character: %r" % cur_char, location)
def scan_over_(self, valid):
p = self.pos_
@@ -175,20 +177,44 @@ class Lexer(object):
tag = tag.strip()
self.scan_until_(Lexer.CHAR_NEWLINE_)
self.scan_over_(Lexer.CHAR_NEWLINE_)
- regexp = r'}\s*' + tag + r'\s*;'
- split = re.split(regexp, self.text_[self.pos_:], maxsplit=1)
+ regexp = r"}\s*" + tag + r"\s*;"
+ split = re.split(regexp, self.text_[self.pos_ :], maxsplit=1)
if len(split) != 2:
raise FeatureLibError(
- "Expected '} %s;' to terminate anonymous block" % tag,
- location)
+ "Expected '} %s;' to terminate anonymous block" % tag, location
+ )
self.pos_ += len(split[0])
return (Lexer.ANONYMOUS_BLOCK, split[0], location)
class IncludingLexer(object):
- def __init__(self, featurefile):
+ """A Lexer that follows include statements.
+
+ The OpenType feature file specification states that due to
+ historical reasons, relative imports should be resolved in this
+ order:
+
+ 1. If the source font is UFO format, then relative to the UFO's
+ font directory
+ 2. relative to the top-level include file
+ 3. relative to the parent include file
+
+ We only support 1 (via includeDir) and 2.
+ """
+
+ def __init__(self, featurefile, *, includeDir=None):
+ """Initializes an IncludingLexer.
+
+ Behavior:
+ If includeDir is passed, it will be used to determine the top-level
+ include directory to use for all encountered include statements. If it is
+ not passed, ``os.path.dirname(featurefile)`` will be considered the
+ include directory.
+ """
+
self.lexers_ = [self.make_lexer_(featurefile)]
self.featurefilepath = self.lexers_[0].filename_
+ self.includeDir = includeDir
def __iter__(self):
return self
@@ -208,13 +234,15 @@ class IncludingLexer(object):
fname_type, fname_token, fname_location = lexer.next()
if fname_type is not Lexer.FILENAME:
raise FeatureLibError("Expected file name", fname_location)
- #semi_type, semi_token, semi_location = lexer.next()
- #if semi_type is not Lexer.SYMBOL or semi_token != ";":
+ # semi_type, semi_token, semi_location = lexer.next()
+ # if semi_type is not Lexer.SYMBOL or semi_token != ";":
# raise FeatureLibError("Expected ';'", semi_location)
if os.path.isabs(fname_token):
path = fname_token
else:
- if self.featurefilepath is not None:
+ if self.includeDir is not None:
+ curpath = self.includeDir
+ elif self.featurefilepath is not None:
curpath = os.path.dirname(self.featurefilepath)
else:
# if the IncludingLexer was initialized from an in-memory
@@ -224,16 +252,11 @@ class IncludingLexer(object):
curpath = os.getcwd()
path = os.path.join(curpath, fname_token)
if len(self.lexers_) >= 5:
- raise FeatureLibError("Too many recursive includes",
- fname_location)
+ raise FeatureLibError("Too many recursive includes", fname_location)
try:
self.lexers_.append(self.make_lexer_(path))
- except IOError as err:
- # FileNotFoundError does not exist on Python < 3.3
- import errno
- if err.errno == errno.ENOENT:
- raise IncludedFeaNotFound(fname_token, fname_location)
- raise # pragma: no cover
+ except FileNotFoundError as err:
+ raise IncludedFeaNotFound(fname_token, fname_location) from err
else:
return (token_type, token, location)
raise StopIteration()
@@ -257,5 +280,6 @@ class IncludingLexer(object):
class NonIncludingLexer(IncludingLexer):
"""Lexer that does not follow `include` statements, emits them as-is."""
+
def __next__(self): # Python 3
return next(self.lexers_[0])
diff --git a/Lib/fontTools/feaLib/location.py b/Lib/fontTools/feaLib/location.py
new file mode 100644
index 00000000..50f761d2
--- /dev/null
+++ b/Lib/fontTools/feaLib/location.py
@@ -0,0 +1,12 @@
+from typing import NamedTuple
+
+
+class FeatureLibLocation(NamedTuple):
+ """A location in a feature file"""
+
+ file: str
+ line: int
+ column: int
+
+ def __str__(self):
+ return f"{self.file}:{self.line}:{self.column}"
diff --git a/Lib/fontTools/feaLib/lookupDebugInfo.py b/Lib/fontTools/feaLib/lookupDebugInfo.py
new file mode 100644
index 00000000..876cadff
--- /dev/null
+++ b/Lib/fontTools/feaLib/lookupDebugInfo.py
@@ -0,0 +1,11 @@
+from typing import NamedTuple
+
+LOOKUP_DEBUG_INFO_KEY = "com.github.fonttools.feaLib"
+LOOKUP_DEBUG_ENV_VAR = "FONTTOOLS_LOOKUP_DEBUGGING"
+
+class LookupDebugInfo(NamedTuple):
+ """Information about where a lookup came from, to be embedded in a font"""
+
+ location: str
+ name: str
+ feature: list
diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py
index 9edfc46f..804cba9f 100644
--- a/Lib/fontTools/feaLib/parser.py
+++ b/Lib/fontTools/feaLib/parser.py
@@ -1,9 +1,7 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.lexer import Lexer, IncludingLexer, NonIncludingLexer
from fontTools.misc.encodingTools import getEncoding
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, tobytes, tostr
import fontTools.feaLib.ast as ast
import logging
import os
@@ -14,24 +12,53 @@ log = logging.getLogger(__name__)
class Parser(object):
+ """Initializes a Parser object.
+
+ Example:
+
+ .. code:: python
+
+ from fontTools.feaLib.parser import Parser
+ parser = Parser(file, font.getReverseGlyphMap())
+ parsetree = parser.parse()
+
+ Note: the ``glyphNames`` iterable serves a double role to help distinguish
+ glyph names from ranges in the presence of hyphens and to ensure that glyph
+ names referenced in a feature file are actually part of a font's glyph set.
+ If the iterable is left empty, no glyph name in glyph set checking takes
+ place, and all glyph tokens containing hyphens are treated as literal glyph
+ names, not as ranges. (Adding a space around the hyphen can, in any case,
+ help to disambiguate ranges from glyph names containing hyphens.)
+
+ By default, the parser will follow ``include()`` statements in the feature
+ file. To turn this off, pass ``followIncludes=False``. Pass a directory string as
+ ``includeDir`` to explicitly declare a directory to search included feature files
+ in.
+ """
+
extensions = {}
ast = ast
- SS_FEATURE_TAGS = {"ss%02d" % i for i in range(1, 20+1)}
- CV_FEATURE_TAGS = {"cv%02d" % i for i in range(1, 99+1)}
+ SS_FEATURE_TAGS = {"ss%02d" % i for i in range(1, 20 + 1)}
+ CV_FEATURE_TAGS = {"cv%02d" % i for i in range(1, 99 + 1)}
+
+ def __init__(
+ self, featurefile, glyphNames=(), followIncludes=True, includeDir=None, **kwargs
+ ):
- def __init__(self, featurefile, glyphNames=(), followIncludes=True,
- **kwargs):
if "glyphMap" in kwargs:
from fontTools.misc.loggingTools import deprecateArgument
+
deprecateArgument("glyphMap", "use 'glyphNames' (iterable) instead")
if glyphNames:
- raise TypeError("'glyphNames' and (deprecated) 'glyphMap' are "
- "mutually exclusive")
+ raise TypeError(
+ "'glyphNames' and (deprecated) 'glyphMap' are " "mutually exclusive"
+ )
glyphNames = kwargs.pop("glyphMap")
if kwargs:
- raise TypeError("unsupported keyword argument%s: %s"
- % ("" if len(kwargs) == 1 else "s",
- ", ".join(repr(k) for k in kwargs)))
+ raise TypeError(
+ "unsupported keyword argument%s: %s"
+ % ("" if len(kwargs) == 1 else "s", ", ".join(repr(k) for k in kwargs))
+ )
self.glyphNames_ = set(glyphNames)
self.doc_ = self.ast.FeatureFile()
@@ -39,24 +66,25 @@ class Parser(object):
self.glyphclasses_ = SymbolTable()
self.lookups_ = SymbolTable()
self.valuerecords_ = SymbolTable()
- self.symbol_tables_ = {
- self.anchors_, self.valuerecords_
- }
+ self.symbol_tables_ = {self.anchors_, self.valuerecords_}
self.next_token_type_, self.next_token_ = (None, None)
self.cur_comments_ = []
self.next_token_location_ = None
lexerClass = IncludingLexer if followIncludes else NonIncludingLexer
- self.lexer_ = lexerClass(featurefile)
+ self.lexer_ = lexerClass(featurefile, includeDir=includeDir)
self.advance_lexer_(comments=True)
def parse(self):
+ """Parse the file, and return a :class:`fontTools.feaLib.ast.FeatureFile`
+ object representing the root of the abstract syntax tree containing the
+ parsed contents of the file."""
statements = self.doc_.statements
while self.next_token_type_ is not None or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
statements.append(
- self.ast.Comment(self.cur_token_,
- location=self.cur_token_location_))
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.is_cur_keyword_("include"):
statements.append(self.parse_include_())
elif self.cur_token_type_ is Lexer.GLYPHCLASS:
@@ -76,65 +104,80 @@ class Parser(object):
elif self.is_cur_keyword_("table"):
statements.append(self.parse_table_())
elif self.is_cur_keyword_("valueRecordDef"):
- statements.append(
- self.parse_valuerecord_definition_(vertical=False))
- elif self.cur_token_type_ is Lexer.NAME and self.cur_token_ in self.extensions:
+ statements.append(self.parse_valuerecord_definition_(vertical=False))
+ elif (
+ self.cur_token_type_ is Lexer.NAME
+ and self.cur_token_ in self.extensions
+ ):
statements.append(self.extensions[self.cur_token_](self))
elif self.cur_token_type_ is Lexer.SYMBOL and self.cur_token_ == ";":
continue
else:
raise FeatureLibError(
"Expected feature, languagesystem, lookup, markClass, "
- "table, or glyph class definition, got {} \"{}\"".format(self.cur_token_type_, self.cur_token_),
- self.cur_token_location_)
+ 'table, or glyph class definition, got {} "{}"'.format(
+ self.cur_token_type_, self.cur_token_
+ ),
+ self.cur_token_location_,
+ )
return self.doc_
def parse_anchor_(self):
+ # Parses an anchor in any of the four formats given in the feature
+ # file specification (2.e.vii).
self.expect_symbol_("<")
self.expect_keyword_("anchor")
location = self.cur_token_location_
- if self.next_token_ == "NULL":
+ if self.next_token_ == "NULL": # Format D
self.expect_keyword_("NULL")
self.expect_symbol_(">")
return None
- if self.next_token_type_ == Lexer.NAME:
+ if self.next_token_type_ == Lexer.NAME: # Format E
name = self.expect_name_()
anchordef = self.anchors_.resolve(name)
if anchordef is None:
raise FeatureLibError(
- 'Unknown anchor "%s"' % name,
- self.cur_token_location_)
+ 'Unknown anchor "%s"' % name, self.cur_token_location_
+ )
self.expect_symbol_(">")
- return self.ast.Anchor(anchordef.x, anchordef.y,
- name=name,
- contourpoint=anchordef.contourpoint,
- xDeviceTable=None, yDeviceTable=None,
- location=location)
+ return self.ast.Anchor(
+ anchordef.x,
+ anchordef.y,
+ name=name,
+ contourpoint=anchordef.contourpoint,
+ xDeviceTable=None,
+ yDeviceTable=None,
+ location=location,
+ )
x, y = self.expect_number_(), self.expect_number_()
contourpoint = None
- if self.next_token_ == "contourpoint":
+ if self.next_token_ == "contourpoint": # Format B
self.expect_keyword_("contourpoint")
contourpoint = self.expect_number_()
- if self.next_token_ == "<":
+ if self.next_token_ == "<": # Format C
xDeviceTable = self.parse_device_()
yDeviceTable = self.parse_device_()
else:
xDeviceTable, yDeviceTable = None, None
self.expect_symbol_(">")
- return self.ast.Anchor(x, y, name=None,
- contourpoint=contourpoint,
- xDeviceTable=xDeviceTable,
- yDeviceTable=yDeviceTable,
- location=location)
+ return self.ast.Anchor(
+ x,
+ y,
+ name=None,
+ contourpoint=contourpoint,
+ xDeviceTable=xDeviceTable,
+ yDeviceTable=yDeviceTable,
+ location=location,
+ )
def parse_anchor_marks_(self):
- """Parses a sequence of [<anchor> mark @MARKCLASS]*."""
+ # Parses a sequence of ``[<anchor> mark @MARKCLASS]*.``
anchorMarks = [] # [(self.ast.Anchor, markClassName)*]
while self.next_token_ == "<":
anchor = self.parse_anchor_()
@@ -146,6 +189,7 @@ class Parser(object):
return anchorMarks
def parse_anchordef_(self):
+ # Parses a named anchor definition (`section 2.e.viii <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#2.e.vii>`_).
assert self.is_cur_keyword_("anchorDef")
location = self.cur_token_location_
x, y = self.expect_number_(), self.expect_number_()
@@ -155,24 +199,26 @@ class Parser(object):
contourpoint = self.expect_number_()
name = self.expect_name_()
self.expect_symbol_(";")
- anchordef = self.ast.AnchorDefinition(name, x, y,
- contourpoint=contourpoint,
- location=location)
+ anchordef = self.ast.AnchorDefinition(
+ name, x, y, contourpoint=contourpoint, location=location
+ )
self.anchors_.define(name, anchordef)
return anchordef
def parse_anonymous_(self):
+ # Parses an anonymous data block (`section 10 <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#10>`_).
assert self.is_cur_keyword_(("anon", "anonymous"))
tag = self.expect_tag_()
_, content, location = self.lexer_.scan_anonymous_block(tag)
self.advance_lexer_()
- self.expect_symbol_('}')
+ self.expect_symbol_("}")
end_tag = self.expect_tag_()
assert tag == end_tag, "bad splitting in Lexer.scan_anonymous_block()"
- self.expect_symbol_(';')
+ self.expect_symbol_(";")
return self.ast.AnonymousBlock(tag, content, location=location)
def parse_attach_(self):
+ # Parses a GDEF Attach statement (`section 9.b <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.b>`_)
assert self.is_cur_keyword_("Attach")
location = self.cur_token_location_
glyphs = self.parse_glyphclass_(accept_glyphname=True)
@@ -180,16 +226,16 @@ class Parser(object):
while self.next_token_ != ";":
contourPoints.add(self.expect_number_())
self.expect_symbol_(";")
- return self.ast.AttachStatement(glyphs, contourPoints,
- location=location)
+ return self.ast.AttachStatement(glyphs, contourPoints, location=location)
def parse_enumerate_(self, vertical):
+ # Parse an enumerated pair positioning rule (`section 6.b.ii <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#6.b.ii>`_).
assert self.cur_token_ in {"enumerate", "enum"}
self.advance_lexer_()
return self.parse_position_(enumerated=True, vertical=vertical)
def parse_GlyphClassDef_(self):
- """Parses 'GlyphClassDef @BASE, @LIGATURES, @MARKS, @COMPONENTS;'"""
+ # Parses 'GlyphClassDef @BASE, @LIGATURES, @MARKS, @COMPONENTS;'
assert self.is_cur_keyword_("GlyphClassDef")
location = self.cur_token_location_
if self.next_token_ != ",":
@@ -212,18 +258,17 @@ class Parser(object):
else:
componentGlyphs = None
self.expect_symbol_(";")
- return self.ast.GlyphClassDefStatement(baseGlyphs, markGlyphs,
- ligatureGlyphs, componentGlyphs,
- location=location)
+ return self.ast.GlyphClassDefStatement(
+ baseGlyphs, markGlyphs, ligatureGlyphs, componentGlyphs, location=location
+ )
def parse_glyphclass_definition_(self):
- """Parses glyph class definitions such as '@UPPERCASE = [A-Z];'"""
+ # Parses glyph class definitions such as '@UPPERCASE = [A-Z];'
location, name = self.cur_token_location_, self.cur_token_
self.expect_symbol_("=")
glyphs = self.parse_glyphclass_(accept_glyphname=False)
self.expect_symbol_(";")
- glyphclass = self.ast.GlyphClassDefinition(name, glyphs,
- location=location)
+ glyphclass = self.ast.GlyphClassDefinition(name, glyphs, location=location)
self.glyphclasses_.define(name, glyphclass)
return glyphclass
@@ -257,19 +302,29 @@ class Parser(object):
return start, limit
elif len(solutions) == 0:
raise FeatureLibError(
- "\"%s\" is not a glyph in the font, and it can not be split "
- "into a range of known glyphs" % name, location)
+ '"%s" is not a glyph in the font, and it can not be split '
+ "into a range of known glyphs" % name,
+ location,
+ )
else:
- ranges = " or ".join(["\"%s - %s\"" % (s, l) for s, l in solutions])
+ ranges = " or ".join(['"%s - %s"' % (s, l) for s, l in solutions])
raise FeatureLibError(
- "Ambiguous glyph range \"%s\"; "
+ 'Ambiguous glyph range "%s"; '
"please use %s to clarify what you mean" % (name, ranges),
- location)
+ location,
+ )
- def parse_glyphclass_(self, accept_glyphname):
- if (accept_glyphname and
- self.next_token_type_ in (Lexer.NAME, Lexer.CID)):
+ def parse_glyphclass_(self, accept_glyphname, accept_null=False):
+ # Parses a glyph class, either named or anonymous, or (if
+ # ``bool(accept_glyphname)``) a glyph name. If ``bool(accept_null)`` then
+ # also accept the special NULL glyph.
+ if accept_glyphname and self.next_token_type_ in (Lexer.NAME, Lexer.CID):
+ if accept_null and self.next_token_ == "NULL":
+ # If you want a glyph called NULL, you should escape it.
+ self.advance_lexer_()
+ return self.ast.NullGlyph(location=self.cur_token_location_)
glyph = self.expect_glyph_()
+ self.check_glyph_name_in_glyph_set(glyph)
return self.ast.GlyphName(glyph, location=self.cur_token_location_)
if self.next_token_type_ is Lexer.GLYPHCLASS:
self.advance_lexer_()
@@ -277,13 +332,12 @@ class Parser(object):
if gc is None:
raise FeatureLibError(
"Unknown glyph class @%s" % self.cur_token_,
- self.cur_token_location_)
+ self.cur_token_location_,
+ )
if isinstance(gc, self.ast.MarkClass):
- return self.ast.MarkClassName(
- gc, location=self.cur_token_location_)
+ return self.ast.MarkClassName(gc, location=self.cur_token_location_)
else:
- return self.ast.GlyphClassName(
- gc, location=self.cur_token_location_)
+ return self.ast.GlyphClassName(gc, location=self.cur_token_location_)
self.expect_symbol_("[")
location = self.cur_token_location_
@@ -292,19 +346,31 @@ class Parser(object):
if self.next_token_type_ is Lexer.NAME:
glyph = self.expect_glyph_()
location = self.cur_token_location_
- if '-' in glyph and glyph not in self.glyphNames_:
+ if "-" in glyph and self.glyphNames_ and glyph not in self.glyphNames_:
start, limit = self.split_glyph_range_(glyph, location)
+ self.check_glyph_name_in_glyph_set(start, limit)
glyphs.add_range(
- start, limit,
- self.make_glyph_range_(location, start, limit))
+ start, limit, self.make_glyph_range_(location, start, limit)
+ )
elif self.next_token_ == "-":
start = glyph
self.expect_symbol_("-")
limit = self.expect_glyph_()
+ self.check_glyph_name_in_glyph_set(start, limit)
glyphs.add_range(
- start, limit,
- self.make_glyph_range_(location, start, limit))
+ start, limit, self.make_glyph_range_(location, start, limit)
+ )
else:
+ if "-" in glyph and not self.glyphNames_:
+ log.warning(
+ str(
+ FeatureLibError(
+ f"Ambiguous glyph name that looks like a range: {glyph!r}",
+ location,
+ )
+ )
+ )
+ self.check_glyph_name_in_glyph_set(glyph)
glyphs.append(glyph)
elif self.next_token_type_ is Lexer.CID:
glyph = self.expect_glyph_()
@@ -313,48 +379,47 @@ class Parser(object):
range_start = self.cur_token_
self.expect_symbol_("-")
range_end = self.expect_cid_()
- glyphs.add_cid_range(range_start, range_end,
- self.make_cid_range_(range_location,
- range_start, range_end))
+ self.check_glyph_name_in_glyph_set(
+ f"cid{range_start:05d}",
+ f"cid{range_end:05d}",
+ )
+ glyphs.add_cid_range(
+ range_start,
+ range_end,
+ self.make_cid_range_(range_location, range_start, range_end),
+ )
else:
- glyphs.append("cid%05d" % self.cur_token_)
+ glyph_name = f"cid{self.cur_token_:05d}"
+ self.check_glyph_name_in_glyph_set(glyph_name)
+ glyphs.append(glyph_name)
elif self.next_token_type_ is Lexer.GLYPHCLASS:
self.advance_lexer_()
gc = self.glyphclasses_.resolve(self.cur_token_)
if gc is None:
raise FeatureLibError(
"Unknown glyph class @%s" % self.cur_token_,
- self.cur_token_location_)
+ self.cur_token_location_,
+ )
if isinstance(gc, self.ast.MarkClass):
- gc = self.ast.MarkClassName(
- gc, location=self.cur_token_location_)
+ gc = self.ast.MarkClassName(gc, location=self.cur_token_location_)
else:
- gc = self.ast.GlyphClassName(
- gc, location=self.cur_token_location_)
+ gc = self.ast.GlyphClassName(gc, location=self.cur_token_location_)
glyphs.add_class(gc)
else:
raise FeatureLibError(
"Expected glyph name, glyph range, "
- "or glyph class reference",
- self.next_token_location_)
+ f"or glyph class reference, found {self.next_token_!r}",
+ self.next_token_location_,
+ )
self.expect_symbol_("]")
return glyphs
- def parse_class_name_(self):
- name = self.expect_class_name_()
- gc = self.glyphclasses_.resolve(name)
- if gc is None:
- raise FeatureLibError(
- "Unknown glyph class @%s" % name,
- self.cur_token_location_)
- if isinstance(gc, self.ast.MarkClass):
- return self.ast.MarkClassName(
- gc, location=self.cur_token_location_)
- else:
- return self.ast.GlyphClassName(
- gc, location=self.cur_token_location_)
-
def parse_glyph_pattern_(self, vertical):
+ # Parses a glyph pattern, including lookups and context, e.g.::
+ #
+ # a b
+ # a b c' d e
+ # a b c' lookup ChangeC d e
prefix, glyphs, lookups, values, suffix = ([], [], [], [], [])
hasMarks = False
while self.next_token_ not in {"by", "from", ";", ","}:
@@ -371,7 +436,8 @@ class Parser(object):
raise FeatureLibError(
"Unsupported contextual target sequence: at most "
"one run of marked (') glyph/class names allowed",
- self.cur_token_location_)
+ self.cur_token_location_,
+ )
glyphs.append(gc)
elif glyphs:
suffix.append(gc)
@@ -383,48 +449,64 @@ class Parser(object):
else:
values.append(None)
- lookup = None
- if self.next_token_ == "lookup":
+ lookuplist = None
+ while self.next_token_ == "lookup":
+ if lookuplist is None:
+ lookuplist = []
self.expect_keyword_("lookup")
if not marked:
raise FeatureLibError(
"Lookups can only follow marked glyphs",
- self.cur_token_location_)
+ self.cur_token_location_,
+ )
lookup_name = self.expect_name_()
lookup = self.lookups_.resolve(lookup_name)
if lookup is None:
raise FeatureLibError(
- 'Unknown lookup "%s"' % lookup_name,
- self.cur_token_location_)
+ 'Unknown lookup "%s"' % lookup_name, self.cur_token_location_
+ )
+ lookuplist.append(lookup)
if marked:
- lookups.append(lookup)
+ lookups.append(lookuplist)
if not glyphs and not suffix: # eg., "sub f f i by"
assert lookups == []
return ([], prefix, [None] * len(prefix), values, [], hasMarks)
else:
- assert not any(values[:len(prefix)]), values
- format1 = values[len(prefix):][:len(glyphs)]
- format2 = values[(len(prefix) + len(glyphs)):][:len(suffix)]
- values = format2 if format2 and isinstance(format2[0], self.ast.ValueRecord) else format1
+ assert not any(values[: len(prefix)]), values
+ format1 = values[len(prefix) :][: len(glyphs)]
+ format2 = values[(len(prefix) + len(glyphs)) :][: len(suffix)]
+ values = (
+ format2
+ if format2 and isinstance(format2[0], self.ast.ValueRecord)
+ else format1
+ )
return (prefix, glyphs, lookups, values, suffix, hasMarks)
def parse_chain_context_(self):
location = self.cur_token_location_
- prefix, glyphs, lookups, values, suffix, hasMarks = \
- self.parse_glyph_pattern_(vertical=False)
+ prefix, glyphs, lookups, values, suffix, hasMarks = self.parse_glyph_pattern_(
+ vertical=False
+ )
chainContext = [(prefix, glyphs, suffix)]
hasLookups = any(lookups)
while self.next_token_ == ",":
self.expect_symbol_(",")
- prefix, glyphs, lookups, values, suffix, hasMarks = \
- self.parse_glyph_pattern_(vertical=False)
+ (
+ prefix,
+ glyphs,
+ lookups,
+ values,
+ suffix,
+ hasMarks,
+ ) = self.parse_glyph_pattern_(vertical=False)
chainContext.append((prefix, glyphs, suffix))
hasLookups = hasLookups or any(lookups)
self.expect_symbol_(";")
return chainContext, hasLookups
def parse_ignore_(self):
+ # Parses an ignore sub/pos rule.
assert self.is_cur_keyword_("ignore")
location = self.cur_token_location_
self.advance_lexer_()
@@ -432,21 +514,19 @@ class Parser(object):
chainContext, hasLookups = self.parse_chain_context_()
if hasLookups:
raise FeatureLibError(
- "No lookups can be specified for \"ignore sub\"",
- location)
- return self.ast.IgnoreSubstStatement(chainContext,
- location=location)
+ 'No lookups can be specified for "ignore sub"', location
+ )
+ return self.ast.IgnoreSubstStatement(chainContext, location=location)
if self.cur_token_ in ["position", "pos"]:
chainContext, hasLookups = self.parse_chain_context_()
if hasLookups:
raise FeatureLibError(
- "No lookups can be specified for \"ignore pos\"",
- location)
- return self.ast.IgnorePosStatement(chainContext,
- location=location)
+ 'No lookups can be specified for "ignore pos"', location
+ )
+ return self.ast.IgnorePosStatement(chainContext, location=location)
raise FeatureLibError(
- "Expected \"substitute\" or \"position\"",
- self.cur_token_location_)
+ 'Expected "substitute" or "position"', self.cur_token_location_
+ )
def parse_include_(self):
assert self.cur_token_ == "include"
@@ -461,14 +541,14 @@ class Parser(object):
language = self.expect_language_tag_()
include_default, required = (True, False)
if self.next_token_ in {"exclude_dflt", "include_dflt"}:
- include_default = (self.expect_name_() == "include_dflt")
+ include_default = self.expect_name_() == "include_dflt"
if self.next_token_ == "required":
self.expect_keyword_("required")
required = True
self.expect_symbol_(";")
- return self.ast.LanguageStatement(language,
- include_default, required,
- location=location)
+ return self.ast.LanguageStatement(
+ language, include_default, required, location=location
+ )
def parse_ligatureCaretByIndex_(self):
assert self.is_cur_keyword_("LigatureCaretByIndex")
@@ -478,8 +558,7 @@ class Parser(object):
while self.next_token_ != ";":
carets.append(self.expect_number_())
self.expect_symbol_(";")
- return self.ast.LigatureCaretByIndexStatement(glyphs, carets,
- location=location)
+ return self.ast.LigatureCaretByIndexStatement(glyphs, carets, location=location)
def parse_ligatureCaretByPos_(self):
assert self.is_cur_keyword_("LigatureCaretByPos")
@@ -489,21 +568,22 @@ class Parser(object):
while self.next_token_ != ";":
carets.append(self.expect_number_())
self.expect_symbol_(";")
- return self.ast.LigatureCaretByPosStatement(glyphs, carets,
- location=location)
+ return self.ast.LigatureCaretByPosStatement(glyphs, carets, location=location)
def parse_lookup_(self, vertical):
+ # Parses a ``lookup`` - either a lookup block, or a lookup reference
+ # inside a feature.
assert self.is_cur_keyword_("lookup")
location, name = self.cur_token_location_, self.expect_name_()
if self.next_token_ == ";":
lookup = self.lookups_.resolve(name)
if lookup is None:
- raise FeatureLibError("Unknown lookup \"%s\"" % name,
- self.cur_token_location_)
+ raise FeatureLibError(
+ 'Unknown lookup "%s"' % name, self.cur_token_location_
+ )
self.expect_symbol_(";")
- return self.ast.LookupReferenceStatement(lookup,
- location=location)
+ return self.ast.LookupReferenceStatement(lookup, location=location)
use_extension = False
if self.next_token_ == "useExtension":
@@ -516,6 +596,8 @@ class Parser(object):
return block
def parse_lookupflag_(self):
+ # Parses a ``lookupflag`` statement, either specified by number or
+ # in words.
assert self.is_cur_keyword_("lookupflag")
location = self.cur_token_location_
@@ -529,39 +611,46 @@ class Parser(object):
value_seen = False
value, markAttachment, markFilteringSet = 0, None, None
flags = {
- "RightToLeft": 1, "IgnoreBaseGlyphs": 2,
- "IgnoreLigatures": 4, "IgnoreMarks": 8
+ "RightToLeft": 1,
+ "IgnoreBaseGlyphs": 2,
+ "IgnoreLigatures": 4,
+ "IgnoreMarks": 8,
}
seen = set()
while self.next_token_ != ";":
if self.next_token_ in seen:
raise FeatureLibError(
"%s can be specified only once" % self.next_token_,
- self.next_token_location_)
+ self.next_token_location_,
+ )
seen.add(self.next_token_)
if self.next_token_ == "MarkAttachmentType":
self.expect_keyword_("MarkAttachmentType")
- markAttachment = self.parse_class_name_()
+ markAttachment = self.parse_glyphclass_(accept_glyphname=False)
elif self.next_token_ == "UseMarkFilteringSet":
self.expect_keyword_("UseMarkFilteringSet")
- markFilteringSet = self.parse_class_name_()
+ markFilteringSet = self.parse_glyphclass_(accept_glyphname=False)
elif self.next_token_ in flags:
value_seen = True
value = value | flags[self.expect_name_()]
else:
raise FeatureLibError(
'"%s" is not a recognized lookupflag' % self.next_token_,
- self.next_token_location_)
+ self.next_token_location_,
+ )
self.expect_symbol_(";")
if not any([value_seen, markAttachment, markFilteringSet]):
raise FeatureLibError(
- 'lookupflag must have a value', self.next_token_location_)
+ "lookupflag must have a value", self.next_token_location_
+ )
- return self.ast.LookupFlagStatement(value,
- markAttachment=markAttachment,
- markFilteringSet=markFilteringSet,
- location=location)
+ return self.ast.LookupFlagStatement(
+ value,
+ markAttachment=markAttachment,
+ markFilteringSet=markFilteringSet,
+ location=location,
+ )
def parse_markClass_(self):
assert self.is_cur_keyword_("markClass")
@@ -575,8 +664,9 @@ class Parser(object):
markClass = self.ast.MarkClass(name)
self.doc_.markClasses[name] = markClass
self.glyphclasses_.define(name, markClass)
- mcdef = self.ast.MarkClassDefinition(markClass, anchor, glyphs,
- location=location)
+ mcdef = self.ast.MarkClassDefinition(
+ markClass, anchor, glyphs, location=location
+ )
markClass.addDefinition(mcdef)
return mcdef
@@ -584,26 +674,28 @@ class Parser(object):
assert self.cur_token_ in {"position", "pos"}
if self.next_token_ == "cursive": # GPOS type 3
return self.parse_position_cursive_(enumerated, vertical)
- elif self.next_token_ == "base": # GPOS type 4
+ elif self.next_token_ == "base": # GPOS type 4
return self.parse_position_base_(enumerated, vertical)
- elif self.next_token_ == "ligature": # GPOS type 5
+ elif self.next_token_ == "ligature": # GPOS type 5
return self.parse_position_ligature_(enumerated, vertical)
- elif self.next_token_ == "mark": # GPOS type 6
+ elif self.next_token_ == "mark": # GPOS type 6
return self.parse_position_mark_(enumerated, vertical)
location = self.cur_token_location_
- prefix, glyphs, lookups, values, suffix, hasMarks = \
- self.parse_glyph_pattern_(vertical)
+ prefix, glyphs, lookups, values, suffix, hasMarks = self.parse_glyph_pattern_(
+ vertical
+ )
self.expect_symbol_(";")
if any(lookups):
# GPOS type 8: Chaining contextual positioning; explicit lookups
if any(values):
raise FeatureLibError(
- "If \"lookup\" is present, no values must be specified",
- location)
+ 'If "lookup" is present, no values must be specified', location
+ )
return self.ast.ChainContextPosStatement(
- prefix, glyphs, suffix, lookups, location=location)
+ prefix, glyphs, suffix, lookups, location=location
+ )
# Pair positioning, format A: "pos V 10 A -10;"
# Pair positioning, format B: "pos V A -20;"
@@ -611,31 +703,41 @@ class Parser(object):
if values[0] is None: # Format B: "pos V A -20;"
values.reverse()
return self.ast.PairPosStatement(
- glyphs[0], values[0], glyphs[1], values[1],
+ glyphs[0],
+ values[0],
+ glyphs[1],
+ values[1],
enumerated=enumerated,
- location=location)
+ location=location,
+ )
if enumerated:
raise FeatureLibError(
- '"enumerate" is only allowed with pair positionings', location)
- return self.ast.SinglePosStatement(list(zip(glyphs, values)),
- prefix, suffix, forceChain=hasMarks,
- location=location)
+ '"enumerate" is only allowed with pair positionings', location
+ )
+ return self.ast.SinglePosStatement(
+ list(zip(glyphs, values)),
+ prefix,
+ suffix,
+ forceChain=hasMarks,
+ location=location,
+ )
def parse_position_cursive_(self, enumerated, vertical):
location = self.cur_token_location_
self.expect_keyword_("cursive")
if enumerated:
raise FeatureLibError(
- '"enumerate" is not allowed with '
- 'cursive attachment positioning',
- location)
+ '"enumerate" is not allowed with ' "cursive attachment positioning",
+ location,
+ )
glyphclass = self.parse_glyphclass_(accept_glyphname=True)
entryAnchor = self.parse_anchor_()
exitAnchor = self.parse_anchor_()
self.expect_symbol_(";")
return self.ast.CursivePosStatement(
- glyphclass, entryAnchor, exitAnchor, location=location)
+ glyphclass, entryAnchor, exitAnchor, location=location
+ )
def parse_position_base_(self, enumerated, vertical):
location = self.cur_token_location_
@@ -643,8 +745,9 @@ class Parser(object):
if enumerated:
raise FeatureLibError(
'"enumerate" is not allowed with '
- 'mark-to-base attachment positioning',
- location)
+ "mark-to-base attachment positioning",
+ location,
+ )
base = self.parse_glyphclass_(accept_glyphname=True)
marks = self.parse_anchor_marks_()
self.expect_symbol_(";")
@@ -656,8 +759,9 @@ class Parser(object):
if enumerated:
raise FeatureLibError(
'"enumerate" is not allowed with '
- 'mark-to-ligature attachment positioning',
- location)
+ "mark-to-ligature attachment positioning",
+ location,
+ )
ligatures = self.parse_glyphclass_(accept_glyphname=True)
marks = [self.parse_anchor_marks_()]
while self.next_token_ == "ligComponent":
@@ -672,13 +776,13 @@ class Parser(object):
if enumerated:
raise FeatureLibError(
'"enumerate" is not allowed with '
- 'mark-to-mark attachment positioning',
- location)
+ "mark-to-mark attachment positioning",
+ location,
+ )
baseMarks = self.parse_glyphclass_(accept_glyphname=True)
marks = self.parse_anchor_marks_()
self.expect_symbol_(";")
- return self.ast.MarkMarkPosStatement(baseMarks, marks,
- location=location)
+ return self.ast.MarkMarkPosStatement(baseMarks, marks, location=location)
def parse_script_(self):
assert self.is_cur_keyword_("script")
@@ -690,16 +794,23 @@ class Parser(object):
assert self.cur_token_ in {"substitute", "sub", "reversesub", "rsub"}
location = self.cur_token_location_
reverse = self.cur_token_ in {"reversesub", "rsub"}
- old_prefix, old, lookups, values, old_suffix, hasMarks = \
- self.parse_glyph_pattern_(vertical=False)
+ (
+ old_prefix,
+ old,
+ lookups,
+ values,
+ old_suffix,
+ hasMarks,
+ ) = self.parse_glyph_pattern_(vertical=False)
if any(values):
raise FeatureLibError(
- "Substitution statements cannot contain values", location)
+ "Substitution statements cannot contain values", location
+ )
new = []
if self.next_token_ == "by":
keyword = self.expect_keyword_("by")
while self.next_token_ != ";":
- gc = self.parse_glyphclass_(accept_glyphname=True)
+ gc = self.parse_glyphclass_(accept_glyphname=True, accept_null=True)
new.append(gc)
elif self.next_token_ == "from":
keyword = self.expect_keyword_("from")
@@ -707,37 +818,41 @@ class Parser(object):
else:
keyword = None
self.expect_symbol_(";")
- if len(new) is 0 and not any(lookups):
+ if len(new) == 0 and not any(lookups):
raise FeatureLibError(
'Expected "by", "from" or explicit lookup references',
- self.cur_token_location_)
+ self.cur_token_location_,
+ )
# GSUB lookup type 3: Alternate substitution.
# Format: "substitute a from [a.1 a.2 a.3];"
if keyword == "from":
if reverse:
raise FeatureLibError(
- 'Reverse chaining substitutions do not support "from"',
- location)
+ 'Reverse chaining substitutions do not support "from"', location
+ )
if len(old) != 1 or len(old[0].glyphSet()) != 1:
- raise FeatureLibError(
- 'Expected a single glyph before "from"',
- location)
+ raise FeatureLibError('Expected a single glyph before "from"', location)
if len(new) != 1:
raise FeatureLibError(
- 'Expected a single glyphclass after "from"',
- location)
+ 'Expected a single glyphclass after "from"', location
+ )
return self.ast.AlternateSubstStatement(
- old_prefix, old[0], old_suffix, new[0], location=location)
+ old_prefix, old[0], old_suffix, new[0], location=location
+ )
num_lookups = len([l for l in lookups if l is not None])
+ is_deletion = False
+ if len(new) == 1 and len(new[0].glyphSet()) == 0:
+ new = [] # Deletion
+ is_deletion = True
+
# GSUB lookup type 1: Single substitution.
# Format A: "substitute a by a.sc;"
# Format B: "substitute [one.fitted one.oldstyle] by one;"
# Format C: "substitute [a-d] by [A.sc-D.sc];"
- if (not reverse and len(old) == 1 and len(new) == 1 and
- num_lookups == 0):
+ if not reverse and len(old) == 1 and len(new) == 1 and num_lookups == 0:
glyphs = list(old[0].glyphSet())
replacements = list(new[0].glyphSet())
if len(replacements) == 1:
@@ -745,36 +860,52 @@ class Parser(object):
if len(glyphs) != len(replacements):
raise FeatureLibError(
'Expected a glyph class with %d elements after "by", '
- 'but found a glyph class with %d elements' %
- (len(glyphs), len(replacements)), location)
+ "but found a glyph class with %d elements"
+ % (len(glyphs), len(replacements)),
+ location,
+ )
return self.ast.SingleSubstStatement(
- old, new,
- old_prefix, old_suffix,
- forceChain=hasMarks,
- location=location
+ old, new, old_prefix, old_suffix, forceChain=hasMarks, location=location
)
# GSUB lookup type 2: Multiple substitution.
# Format: "substitute f_f_i by f f i;"
- if (not reverse and
- len(old) == 1 and len(old[0].glyphSet()) == 1 and
- len(new) > 1 and max([len(n.glyphSet()) for n in new]) == 1 and
- num_lookups == 0):
+ if (
+ not reverse
+ and len(old) == 1
+ and len(old[0].glyphSet()) == 1
+ and (
+ (len(new) > 1 and max([len(n.glyphSet()) for n in new]) == 1)
+ or len(new) == 0
+ )
+ and num_lookups == 0
+ ):
return self.ast.MultipleSubstStatement(
- old_prefix, tuple(old[0].glyphSet())[0], old_suffix,
+ old_prefix,
+ tuple(old[0].glyphSet())[0],
+ old_suffix,
tuple([list(n.glyphSet())[0] for n in new]),
- forceChain=hasMarks, location=location)
+ forceChain=hasMarks,
+ location=location,
+ )
# GSUB lookup type 4: Ligature substitution.
# Format: "substitute f f i by f_f_i;"
- if (not reverse and
- len(old) > 1 and len(new) == 1 and
- len(new[0].glyphSet()) == 1 and
- num_lookups == 0):
+ if (
+ not reverse
+ and len(old) > 1
+ and len(new) == 1
+ and len(new[0].glyphSet()) == 1
+ and num_lookups == 0
+ ):
return self.ast.LigatureSubstStatement(
- old_prefix, old, old_suffix,
- list(new[0].glyphSet())[0], forceChain=hasMarks,
- location=location)
+ old_prefix,
+ old,
+ old_suffix,
+ list(new[0].glyphSet())[0],
+ forceChain=hasMarks,
+ location=location,
+ )
# GSUB lookup type 8: Reverse chaining substitution.
if reverse:
@@ -782,16 +913,19 @@ class Parser(object):
raise FeatureLibError(
"In reverse chaining single substitutions, "
"only a single glyph or glyph class can be replaced",
- location)
+ location,
+ )
if len(new) != 1:
raise FeatureLibError(
- 'In reverse chaining single substitutions, '
+ "In reverse chaining single substitutions, "
'the replacement (after "by") must be a single glyph '
- 'or glyph class', location)
+ "or glyph class",
+ location,
+ )
if num_lookups != 0:
raise FeatureLibError(
- "Reverse chaining substitutions cannot call named lookups",
- location)
+ "Reverse chaining substitutions cannot call named lookups", location
+ )
glyphs = sorted(list(old[0].glyphSet()))
replacements = sorted(list(new[0].glyphSet()))
if len(replacements) == 1:
@@ -799,21 +933,29 @@ class Parser(object):
if len(glyphs) != len(replacements):
raise FeatureLibError(
'Expected a glyph class with %d elements after "by", '
- 'but found a glyph class with %d elements' %
- (len(glyphs), len(replacements)), location)
+ "but found a glyph class with %d elements"
+ % (len(glyphs), len(replacements)),
+ location,
+ )
return self.ast.ReverseChainSingleSubstStatement(
- old_prefix, old_suffix, old, new, location=location)
+ old_prefix, old_suffix, old, new, location=location
+ )
if len(old) > 1 and len(new) > 1:
raise FeatureLibError(
- 'Direct substitution of multiple glyphs by multiple glyphs '
- 'is not supported',
- location)
+ "Direct substitution of multiple glyphs by multiple glyphs "
+ "is not supported",
+ location,
+ )
+
+ # If there are remaining glyphs to parse, this is an invalid GSUB statement
+ if len(new) != 0 or is_deletion:
+ raise FeatureLibError("Invalid substitution statement", location)
# GSUB lookup type 6: Chaining contextual substitution.
- assert len(new) == 0, new
rule = self.ast.ChainContextSubstStatement(
- old_prefix, old, old_suffix, lookups, location=location)
+ old_prefix, old, old_suffix, lookups, location=location
+ )
return rule
def parse_subtable_(self):
@@ -823,29 +965,30 @@ class Parser(object):
return self.ast.SubtableStatement(location=location)
def parse_size_parameters_(self):
+ # Parses a ``parameters`` statement used in ``size`` features. See
+ # `section 8.b <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#8.b>`_.
assert self.is_cur_keyword_("parameters")
location = self.cur_token_location_
DesignSize = self.expect_decipoint_()
SubfamilyID = self.expect_number_()
- RangeStart = 0
- RangeEnd = 0
- if self.next_token_type_ in (Lexer.NUMBER, Lexer.FLOAT) or \
- SubfamilyID != 0:
+ RangeStart = 0.
+ RangeEnd = 0.
+ if self.next_token_type_ in (Lexer.NUMBER, Lexer.FLOAT) or SubfamilyID != 0:
RangeStart = self.expect_decipoint_()
RangeEnd = self.expect_decipoint_()
self.expect_symbol_(";")
- return self.ast.SizeParameters(DesignSize, SubfamilyID,
- RangeStart, RangeEnd,
- location=location)
+ return self.ast.SizeParameters(
+ DesignSize, SubfamilyID, RangeStart, RangeEnd, location=location
+ )
def parse_size_menuname_(self):
assert self.is_cur_keyword_("sizemenuname")
location = self.cur_token_location_
platformID, platEncID, langID, string = self.parse_name_()
- return self.ast.FeatureNameStatement("size", platformID,
- platEncID, langID, string,
- location=location)
+ return self.ast.FeatureNameStatement(
+ "size", platformID, platEncID, langID, string, location=location
+ )
def parse_table_(self):
assert self.is_cur_keyword_("table")
@@ -860,17 +1003,20 @@ class Parser(object):
"name": self.parse_table_name_,
"BASE": self.parse_table_BASE_,
"OS/2": self.parse_table_OS_2_,
+ "STAT": self.parse_table_STAT_,
}.get(name)
if handler:
handler(table)
else:
- raise FeatureLibError('"table %s" is not supported' % name.strip(),
- location)
+ raise FeatureLibError(
+ '"table %s" is not supported' % name.strip(), location
+ )
self.expect_symbol_("}")
end_tag = self.expect_tag_()
if end_tag != name:
- raise FeatureLibError('Expected "%s"' % name.strip(),
- self.cur_token_location_)
+ raise FeatureLibError(
+ 'Expected "%s"' % name.strip(), self.cur_token_location_
+ )
self.expect_symbol_(";")
return table
@@ -879,8 +1025,9 @@ class Parser(object):
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.is_cur_keyword_("Attach"):
statements.append(self.parse_attach_())
elif self.is_cur_keyword_("GlyphClassDef"):
@@ -893,24 +1040,24 @@ class Parser(object):
continue
else:
raise FeatureLibError(
- "Expected Attach, LigatureCaretByIndex, "
- "or LigatureCaretByPos",
- self.cur_token_location_)
+ "Expected Attach, LigatureCaretByIndex, " "or LigatureCaretByPos",
+ self.cur_token_location_,
+ )
def parse_table_head_(self, table):
statements = table.statements
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.is_cur_keyword_("FontRevision"):
statements.append(self.parse_FontRevision_())
elif self.cur_token_ == ";":
continue
else:
- raise FeatureLibError("Expected FontRevision",
- self.cur_token_location_)
+ raise FeatureLibError("Expected FontRevision", self.cur_token_location_)
def parse_table_hhea_(self, table):
statements = table.statements
@@ -918,22 +1065,26 @@ class Parser(object):
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.cur_token_type_ is Lexer.NAME and self.cur_token_ in fields:
key = self.cur_token_.lower()
value = self.expect_number_()
statements.append(
- self.ast.HheaField(key, value,
- location=self.cur_token_location_))
+ self.ast.HheaField(key, value, location=self.cur_token_location_)
+ )
if self.next_token_ != ";":
- raise FeatureLibError("Incomplete statement", self.next_token_location_)
+ raise FeatureLibError(
+ "Incomplete statement", self.next_token_location_
+ )
elif self.cur_token_ == ";":
continue
else:
- raise FeatureLibError("Expected CaretOffset, Ascender, "
- "Descender or LineGap",
- self.cur_token_location_)
+ raise FeatureLibError(
+ "Expected CaretOffset, Ascender, " "Descender or LineGap",
+ self.cur_token_location_,
+ )
def parse_table_vhea_(self, table):
statements = table.statements
@@ -941,30 +1092,36 @@ class Parser(object):
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.cur_token_type_ is Lexer.NAME and self.cur_token_ in fields:
key = self.cur_token_.lower()
value = self.expect_number_()
statements.append(
- self.ast.VheaField(key, value,
- location=self.cur_token_location_))
+ self.ast.VheaField(key, value, location=self.cur_token_location_)
+ )
if self.next_token_ != ";":
- raise FeatureLibError("Incomplete statement", self.next_token_location_)
+ raise FeatureLibError(
+ "Incomplete statement", self.next_token_location_
+ )
elif self.cur_token_ == ";":
continue
else:
- raise FeatureLibError("Expected VertTypoAscender, "
- "VertTypoDescender or VertTypoLineGap",
- self.cur_token_location_)
+ raise FeatureLibError(
+ "Expected VertTypoAscender, "
+ "VertTypoDescender or VertTypoLineGap",
+ self.cur_token_location_,
+ )
def parse_table_name_(self, table):
statements = table.statements
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.is_cur_keyword_("nameid"):
statement = self.parse_nameid_()
if statement:
@@ -972,30 +1129,30 @@ class Parser(object):
elif self.cur_token_ == ";":
continue
else:
- raise FeatureLibError("Expected nameid",
- self.cur_token_location_)
+ raise FeatureLibError("Expected nameid", self.cur_token_location_)
def parse_name_(self):
+ """Parses a name record. See `section 9.e <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.e>`_."""
platEncID = None
langID = None
- if self.next_token_type_ == Lexer.NUMBER:
- platformID = self.expect_number_()
+ if self.next_token_type_ in Lexer.NUMBERS:
+ platformID = self.expect_any_number_()
location = self.cur_token_location_
if platformID not in (1, 3):
raise FeatureLibError("Expected platform id 1 or 3", location)
- if self.next_token_type_ == Lexer.NUMBER:
- platEncID = self.expect_number_()
- langID = self.expect_number_()
+ if self.next_token_type_ in Lexer.NUMBERS:
+ platEncID = self.expect_any_number_()
+ langID = self.expect_any_number_()
else:
platformID = 3
location = self.cur_token_location_
- if platformID == 1: # Macintosh
- platEncID = platEncID or 0 # Roman
- langID = langID or 0 # English
- else: # 3, Windows
- platEncID = platEncID or 1 # Unicode
- langID = langID or 0x0409 # English
+ if platformID == 1: # Macintosh
+ platEncID = platEncID or 0 # Roman
+ langID = langID or 0 # English
+ else: # 3, Windows
+ platEncID = platEncID or 1 # Unicode
+ langID = langID or 0x0409 # English
string = self.expect_string_()
self.expect_symbol_(";")
@@ -1006,21 +1163,54 @@ class Parser(object):
unescaped = self.unescape_string_(string, encoding)
return platformID, platEncID, langID, unescaped
+ def parse_stat_name_(self):
+ platEncID = None
+ langID = None
+ if self.next_token_type_ in Lexer.NUMBERS:
+ platformID = self.expect_any_number_()
+ location = self.cur_token_location_
+ if platformID not in (1, 3):
+ raise FeatureLibError("Expected platform id 1 or 3", location)
+ if self.next_token_type_ in Lexer.NUMBERS:
+ platEncID = self.expect_any_number_()
+ langID = self.expect_any_number_()
+ else:
+ platformID = 3
+ location = self.cur_token_location_
+
+ if platformID == 1: # Macintosh
+ platEncID = platEncID or 0 # Roman
+ langID = langID or 0 # English
+ else: # 3, Windows
+ platEncID = platEncID or 1 # Unicode
+ langID = langID or 0x0409 # English
+
+ string = self.expect_string_()
+ encoding = getEncoding(platformID, platEncID, langID)
+ if encoding is None:
+ raise FeatureLibError("Unsupported encoding", location)
+ unescaped = self.unescape_string_(string, encoding)
+ return platformID, platEncID, langID, unescaped
+
def parse_nameid_(self):
assert self.cur_token_ == "nameid", self.cur_token_
- location, nameID = self.cur_token_location_, self.expect_number_()
+ location, nameID = self.cur_token_location_, self.expect_any_number_()
if nameID > 32767:
- raise FeatureLibError("Name id value cannot be greater than 32767",
- self.cur_token_location_)
+ raise FeatureLibError(
+ "Name id value cannot be greater than 32767", self.cur_token_location_
+ )
if 1 <= nameID <= 6:
- log.warning("Name id %d cannot be set from the feature file. "
- "Ignoring record" % nameID)
+ log.warning(
+ "Name id %d cannot be set from the feature file. "
+ "Ignoring record" % nameID
+ )
self.parse_name_() # skip to the next record
return None
platformID, platEncID, langID, string = self.parse_name_()
- return self.ast.NameRecord(nameID, platformID, platEncID,
- langID, string, location=location)
+ return self.ast.NameRecord(
+ nameID, platformID, platEncID, langID, string, location=location
+ )
def unescape_string_(self, string, encoding):
if encoding == "utf_16_be":
@@ -1032,12 +1222,12 @@ class Parser(object):
# We convert surrogates to actual Unicode by round-tripping through
# Python's UTF-16 codec in a special mode.
utf16 = tobytes(s, "utf_16_be", "surrogatepass")
- return tounicode(utf16, "utf_16_be")
+ return tostr(utf16, "utf_16_be")
@staticmethod
def unescape_unichr_(match):
n = match.group(0)[1:]
- return unichr(int(n, 16))
+ return chr(int(n, 16))
@staticmethod
def unescape_byte_(match, encoding):
@@ -1049,38 +1239,59 @@ class Parser(object):
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.is_cur_keyword_("HorizAxis.BaseTagList"):
horiz_bases = self.parse_base_tag_list_()
elif self.is_cur_keyword_("HorizAxis.BaseScriptList"):
horiz_scripts = self.parse_base_script_list_(len(horiz_bases))
statements.append(
- self.ast.BaseAxis(horiz_bases,
- horiz_scripts, False,
- location=self.cur_token_location_))
+ self.ast.BaseAxis(
+ horiz_bases,
+ horiz_scripts,
+ False,
+ location=self.cur_token_location_,
+ )
+ )
elif self.is_cur_keyword_("VertAxis.BaseTagList"):
vert_bases = self.parse_base_tag_list_()
elif self.is_cur_keyword_("VertAxis.BaseScriptList"):
vert_scripts = self.parse_base_script_list_(len(vert_bases))
statements.append(
- self.ast.BaseAxis(vert_bases,
- vert_scripts, True,
- location=self.cur_token_location_))
+ self.ast.BaseAxis(
+ vert_bases,
+ vert_scripts,
+ True,
+ location=self.cur_token_location_,
+ )
+ )
elif self.cur_token_ == ";":
continue
def parse_table_OS_2_(self, table):
statements = table.statements
- numbers = ("FSType", "TypoAscender", "TypoDescender", "TypoLineGap",
- "winAscent", "winDescent", "XHeight", "CapHeight",
- "WeightClass", "WidthClass", "LowerOpSize", "UpperOpSize")
+ numbers = (
+ "FSType",
+ "TypoAscender",
+ "TypoDescender",
+ "TypoLineGap",
+ "winAscent",
+ "winDescent",
+ "XHeight",
+ "CapHeight",
+ "WeightClass",
+ "WidthClass",
+ "LowerOpSize",
+ "UpperOpSize",
+ )
ranges = ("UnicodeRange", "CodePageRange")
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.cur_token_type_ is Lexer.NAME:
key = self.cur_token_.lower()
value = None
@@ -1093,18 +1304,213 @@ class Parser(object):
elif self.cur_token_ in ranges:
value = []
while self.next_token_ != ";":
- value.append(self.expect_number_())
+ value.append(self.expect_number_())
elif self.is_cur_keyword_("Vendor"):
value = self.expect_string_()
statements.append(
- self.ast.OS2Field(key, value,
- location=self.cur_token_location_))
+ self.ast.OS2Field(key, value, location=self.cur_token_location_)
+ )
+ elif self.cur_token_ == ";":
+ continue
+
+ def parse_STAT_ElidedFallbackName(self):
+ assert self.is_cur_keyword_("ElidedFallbackName")
+ self.expect_symbol_("{")
+ names = []
+ while self.next_token_ != "}" or self.cur_comments_:
+ self.advance_lexer_()
+ if self.is_cur_keyword_("name"):
+ platformID, platEncID, langID, string = self.parse_stat_name_()
+ nameRecord = self.ast.STATNameStatement(
+ "stat",
+ platformID,
+ platEncID,
+ langID,
+ string,
+ location=self.cur_token_location_,
+ )
+ names.append(nameRecord)
+ else:
+ if self.cur_token_ != ";":
+ raise FeatureLibError(
+ f"Unexpected token {self.cur_token_} " f"in ElidedFallbackName",
+ self.cur_token_location_,
+ )
+ self.expect_symbol_("}")
+ if not names:
+ raise FeatureLibError('Expected "name"', self.cur_token_location_)
+ return names
+
+ def parse_STAT_design_axis(self):
+ assert self.is_cur_keyword_("DesignAxis")
+ names = []
+ axisTag = self.expect_tag_()
+ if (
+ axisTag not in ("ital", "opsz", "slnt", "wdth", "wght")
+ and not axisTag.isupper()
+ ):
+ log.warning(f"Unregistered axis tag {axisTag} should be uppercase.")
+ axisOrder = self.expect_number_()
+ self.expect_symbol_("{")
+ while self.next_token_ != "}" or self.cur_comments_:
+ self.advance_lexer_()
+ if self.cur_token_type_ is Lexer.COMMENT:
+ continue
+ elif self.is_cur_keyword_("name"):
+ location = self.cur_token_location_
+ platformID, platEncID, langID, string = self.parse_stat_name_()
+ name = self.ast.STATNameStatement(
+ "stat", platformID, platEncID, langID, string, location=location
+ )
+ names.append(name)
+ elif self.cur_token_ == ";":
+ continue
+ else:
+ raise FeatureLibError(
+ f'Expected "name", got {self.cur_token_}', self.cur_token_location_
+ )
+
+ self.expect_symbol_("}")
+ return self.ast.STATDesignAxisStatement(
+ axisTag, axisOrder, names, self.cur_token_location_
+ )
+
+ def parse_STAT_axis_value_(self):
+ assert self.is_cur_keyword_("AxisValue")
+ self.expect_symbol_("{")
+ locations = []
+ names = []
+ flags = 0
+ while self.next_token_ != "}" or self.cur_comments_:
+ self.advance_lexer_(comments=True)
+ if self.cur_token_type_ is Lexer.COMMENT:
+ continue
+ elif self.is_cur_keyword_("name"):
+ location = self.cur_token_location_
+ platformID, platEncID, langID, string = self.parse_stat_name_()
+ name = self.ast.STATNameStatement(
+ "stat", platformID, platEncID, langID, string, location=location
+ )
+ names.append(name)
+ elif self.is_cur_keyword_("location"):
+ location = self.parse_STAT_location()
+ locations.append(location)
+ elif self.is_cur_keyword_("flag"):
+ flags = self.expect_stat_flags()
+ elif self.cur_token_ == ";":
+ continue
+ else:
+ raise FeatureLibError(
+ f"Unexpected token {self.cur_token_} " f"in AxisValue",
+ self.cur_token_location_,
+ )
+ self.expect_symbol_("}")
+ if not names:
+ raise FeatureLibError('Expected "Axis Name"', self.cur_token_location_)
+ if not locations:
+ raise FeatureLibError('Expected "Axis location"', self.cur_token_location_)
+ if len(locations) > 1:
+ for location in locations:
+ if len(location.values) > 1:
+ raise FeatureLibError(
+ "Only one value is allowed in a "
+ "Format 4 Axis Value Record, but "
+ f"{len(location.values)} were found.",
+ self.cur_token_location_,
+ )
+ format4_tags = []
+ for location in locations:
+ tag = location.tag
+ if tag in format4_tags:
+ raise FeatureLibError(
+ f"Axis tag {tag} already " "defined.", self.cur_token_location_
+ )
+ format4_tags.append(tag)
+
+ return self.ast.STATAxisValueStatement(
+ names, locations, flags, self.cur_token_location_
+ )
+
+ def parse_STAT_location(self):
+ values = []
+ tag = self.expect_tag_()
+ if len(tag.strip()) != 4:
+ raise FeatureLibError(
+ f"Axis tag {self.cur_token_} must be 4 " "characters",
+ self.cur_token_location_,
+ )
+
+ while self.next_token_ != ";":
+ if self.next_token_type_ is Lexer.FLOAT:
+ value = self.expect_float_()
+ values.append(value)
+ elif self.next_token_type_ is Lexer.NUMBER:
+ value = self.expect_number_()
+ values.append(value)
+ else:
+ raise FeatureLibError(
+ f'Unexpected value "{self.next_token_}". '
+ "Expected integer or float.",
+ self.next_token_location_,
+ )
+ if len(values) == 3:
+ nominal, min_val, max_val = values
+ if nominal < min_val or nominal > max_val:
+ raise FeatureLibError(
+ f"Default value {nominal} is outside "
+ f"of specified range "
+ f"{min_val}-{max_val}.",
+ self.next_token_location_,
+ )
+ return self.ast.AxisValueLocationStatement(tag, values)
+
+ def parse_table_STAT_(self, table):
+ statements = table.statements
+ design_axes = []
+ while self.next_token_ != "}" or self.cur_comments_:
+ self.advance_lexer_(comments=True)
+ if self.cur_token_type_ is Lexer.COMMENT:
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
+ elif self.cur_token_type_ is Lexer.NAME:
+ if self.is_cur_keyword_("ElidedFallbackName"):
+ names = self.parse_STAT_ElidedFallbackName()
+ statements.append(self.ast.ElidedFallbackName(names))
+ elif self.is_cur_keyword_("ElidedFallbackNameID"):
+ value = self.expect_number_()
+ statements.append(self.ast.ElidedFallbackNameID(value))
+ self.expect_symbol_(";")
+ elif self.is_cur_keyword_("DesignAxis"):
+ designAxis = self.parse_STAT_design_axis()
+ design_axes.append(designAxis.tag)
+ statements.append(designAxis)
+ self.expect_symbol_(";")
+ elif self.is_cur_keyword_("AxisValue"):
+ axisValueRecord = self.parse_STAT_axis_value_()
+ for location in axisValueRecord.locations:
+ if location.tag not in design_axes:
+ # Tag must be defined in a DesignAxis before it
+ # can be referenced
+ raise FeatureLibError(
+ "DesignAxis not defined for " f"{location.tag}.",
+ self.cur_token_location_,
+ )
+ statements.append(axisValueRecord)
+ self.expect_symbol_(";")
+ else:
+ raise FeatureLibError(
+ f"Unexpected token {self.cur_token_}", self.cur_token_location_
+ )
elif self.cur_token_ == ";":
continue
def parse_base_tag_list_(self):
- assert self.cur_token_ in ("HorizAxis.BaseTagList",
- "VertAxis.BaseTagList"), self.cur_token_
+ # Parses BASE table entries. (See `section 9.a <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.a>`_)
+ assert self.cur_token_ in (
+ "HorizAxis.BaseTagList",
+ "VertAxis.BaseTagList",
+ ), self.cur_token_
bases = []
while self.next_token_ != ";":
bases.append(self.expect_script_tag_())
@@ -1112,8 +1518,10 @@ class Parser(object):
return bases
def parse_base_script_list_(self, count):
- assert self.cur_token_ in ("HorizAxis.BaseScriptList",
- "VertAxis.BaseScriptList"), self.cur_token_
+ assert self.cur_token_ in (
+ "HorizAxis.BaseScriptList",
+ "VertAxis.BaseScriptList",
+ ), self.cur_token_
scripts = [(self.parse_base_script_record_(count))]
while self.next_token_ == ",":
self.expect_symbol_(",")
@@ -1149,13 +1557,13 @@ class Parser(object):
if self.next_token_type_ is Lexer.NUMBER:
number, location = self.expect_number_(), self.cur_token_location_
if vertical:
- val = self.ast.ValueRecord(yAdvance=number,
- vertical=vertical,
- location=location)
+ val = self.ast.ValueRecord(
+ yAdvance=number, vertical=vertical, location=location
+ )
else:
- val = self.ast.ValueRecord(xAdvance=number,
- vertical=vertical,
- location=location)
+ val = self.ast.ValueRecord(
+ xAdvance=number, vertical=vertical, location=location
+ )
return val
self.expect_symbol_("<")
location = self.cur_token_location_
@@ -1166,42 +1574,60 @@ class Parser(object):
return self.ast.ValueRecord()
vrd = self.valuerecords_.resolve(name)
if vrd is None:
- raise FeatureLibError("Unknown valueRecordDef \"%s\"" % name,
- self.cur_token_location_)
+ raise FeatureLibError(
+ 'Unknown valueRecordDef "%s"' % name, self.cur_token_location_
+ )
value = vrd.value
xPlacement, yPlacement = (value.xPlacement, value.yPlacement)
xAdvance, yAdvance = (value.xAdvance, value.yAdvance)
else:
xPlacement, yPlacement, xAdvance, yAdvance = (
- self.expect_number_(), self.expect_number_(),
- self.expect_number_(), self.expect_number_())
+ self.expect_number_(),
+ self.expect_number_(),
+ self.expect_number_(),
+ self.expect_number_(),
+ )
if self.next_token_ == "<":
xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice = (
- self.parse_device_(), self.parse_device_(),
- self.parse_device_(), self.parse_device_())
- allDeltas = sorted([
- delta
- for size, delta
- in (xPlaDevice if xPlaDevice else ()) +
- (yPlaDevice if yPlaDevice else ()) +
- (xAdvDevice if xAdvDevice else ()) +
- (yAdvDevice if yAdvDevice else ())])
+ self.parse_device_(),
+ self.parse_device_(),
+ self.parse_device_(),
+ self.parse_device_(),
+ )
+ allDeltas = sorted(
+ [
+ delta
+ for size, delta in (xPlaDevice if xPlaDevice else ())
+ + (yPlaDevice if yPlaDevice else ())
+ + (xAdvDevice if xAdvDevice else ())
+ + (yAdvDevice if yAdvDevice else ())
+ ]
+ )
if allDeltas[0] < -128 or allDeltas[-1] > 127:
raise FeatureLibError(
"Device value out of valid range (-128..127)",
- self.cur_token_location_)
+ self.cur_token_location_,
+ )
else:
- xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice = (
- None, None, None, None)
+ xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice = (None, None, None, None)
self.expect_symbol_(">")
return self.ast.ValueRecord(
- xPlacement, yPlacement, xAdvance, yAdvance,
- xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice,
- vertical=vertical, location=location)
+ xPlacement,
+ yPlacement,
+ xAdvance,
+ yAdvance,
+ xPlaDevice,
+ yPlaDevice,
+ xAdvDevice,
+ yAdvDevice,
+ vertical=vertical,
+ location=location,
+ )
def parse_valuerecord_definition_(self, vertical):
+ # Parses a named value record definition. (See section `2.e.v <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#2.e.v>`_)
assert self.is_cur_keyword_("valueRecordDef")
location = self.cur_token_location_
value = self.parse_valuerecord_(vertical)
@@ -1217,14 +1643,13 @@ class Parser(object):
script = self.expect_script_tag_()
language = self.expect_language_tag_()
self.expect_symbol_(";")
- return self.ast.LanguageSystemStatement(script, language,
- location=location)
+ return self.ast.LanguageSystemStatement(script, language, location=location)
def parse_feature_block_(self):
assert self.cur_token_ == "feature"
location = self.cur_token_location_
tag = self.expect_tag_()
- vertical = (tag in {"vkrn", "vpal", "vhal", "valt"})
+ vertical = tag in {"vkrn", "vpal", "vhal", "valt"}
stylisticset = None
cv_feature = None
@@ -1241,10 +1666,10 @@ class Parser(object):
self.expect_keyword_("useExtension")
use_extension = True
- block = self.ast.FeatureBlock(tag, use_extension=use_extension,
- location=location)
- self.parse_block_(block, vertical, stylisticset, size_feature,
- cv_feature)
+ block = self.ast.FeatureBlock(
+ tag, use_extension=use_extension, location=location
+ )
+ self.parse_block_(block, vertical, stylisticset, size_feature, cv_feature)
return block
def parse_feature_reference_(self):
@@ -1252,33 +1677,36 @@ class Parser(object):
location = self.cur_token_location_
featureName = self.expect_tag_()
self.expect_symbol_(";")
- return self.ast.FeatureReferenceStatement(featureName,
- location=location)
+ return self.ast.FeatureReferenceStatement(featureName, location=location)
def parse_featureNames_(self, tag):
+ """Parses a ``featureNames`` statement found in stylistic set features.
+ See section `8.c <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#8.c>`_."""
assert self.cur_token_ == "featureNames", self.cur_token_
- block = self.ast.NestedBlock(tag, self.cur_token_,
- location=self.cur_token_location_)
+ block = self.ast.NestedBlock(
+ tag, self.cur_token_, location=self.cur_token_location_
+ )
self.expect_symbol_("{")
for symtab in self.symbol_tables_:
symtab.enter_scope()
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- block.statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
+ block.statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.is_cur_keyword_("name"):
location = self.cur_token_location_
platformID, platEncID, langID, string = self.parse_name_()
block.statements.append(
- self.ast.FeatureNameStatement(tag, platformID,
- platEncID, langID, string,
- location=location))
+ self.ast.FeatureNameStatement(
+ tag, platformID, platEncID, langID, string, location=location
+ )
+ )
elif self.cur_token_ == ";":
continue
else:
- raise FeatureLibError('Expected "name"',
- self.cur_token_location_)
+ raise FeatureLibError('Expected "name"', self.cur_token_location_)
self.expect_symbol_("}")
for symtab in self.symbol_tables_:
symtab.exit_scope()
@@ -1286,9 +1714,12 @@ class Parser(object):
return block
def parse_cvParameters_(self, tag):
+ # Parses a ``cvParameters`` block found in Character Variant features.
+ # See section `8.d <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#8.d>`_.
assert self.cur_token_ == "cvParameters", self.cur_token_
- block = self.ast.NestedBlock(tag, self.cur_token_,
- location=self.cur_token_location_)
+ block = self.ast.NestedBlock(
+ tag, self.cur_token_, location=self.cur_token_location_
+ )
self.expect_symbol_("{")
for symtab in self.symbol_tables_:
symtab.enter_scope()
@@ -1297,12 +1728,17 @@ class Parser(object):
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
- elif self.is_cur_keyword_({"FeatUILabelNameID",
- "FeatUITooltipTextNameID",
- "SampleTextNameID",
- "ParamUILabelNameID"}):
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
+ elif self.is_cur_keyword_(
+ {
+ "FeatUILabelNameID",
+ "FeatUITooltipTextNameID",
+ "SampleTextNameID",
+ "ParamUILabelNameID",
+ }
+ ):
statements.append(self.parse_cvNameIDs_(tag, self.cur_token_))
elif self.is_cur_keyword_("Character"):
statements.append(self.parse_cvCharacter_(tag))
@@ -1311,8 +1747,10 @@ class Parser(object):
else:
raise FeatureLibError(
"Expected statement: got {} {}".format(
- self.cur_token_type_, self.cur_token_),
- self.cur_token_location_)
+ self.cur_token_type_, self.cur_token_
+ ),
+ self.cur_token_location_,
+ )
self.expect_symbol_("}")
for symtab in self.symbol_tables_:
@@ -1322,28 +1760,34 @@ class Parser(object):
def parse_cvNameIDs_(self, tag, block_name):
assert self.cur_token_ == block_name, self.cur_token_
- block = self.ast.NestedBlock(tag, block_name,
- location=self.cur_token_location_)
+ block = self.ast.NestedBlock(tag, block_name, location=self.cur_token_location_)
self.expect_symbol_("{")
for symtab in self.symbol_tables_:
symtab.enter_scope()
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- block.statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
+ block.statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.is_cur_keyword_("name"):
location = self.cur_token_location_
platformID, platEncID, langID, string = self.parse_name_()
block.statements.append(
self.ast.CVParametersNameStatement(
- tag, platformID, platEncID, langID, string,
- block_name, location=location))
+ tag,
+ platformID,
+ platEncID,
+ langID,
+ string,
+ block_name,
+ location=location,
+ )
+ )
elif self.cur_token_ == ";":
continue
else:
- raise FeatureLibError('Expected "name"',
- self.cur_token_location_)
+ raise FeatureLibError('Expected "name"', self.cur_token_location_)
self.expect_symbol_("}")
for symtab in self.symbol_tables_:
symtab.exit_scope()
@@ -1352,25 +1796,29 @@ class Parser(object):
def parse_cvCharacter_(self, tag):
assert self.cur_token_ == "Character", self.cur_token_
- location, character = self.cur_token_location_, self.expect_decimal_or_hexadecimal_()
+ location, character = self.cur_token_location_, self.expect_any_number_()
self.expect_symbol_(";")
if not (0xFFFFFF >= character >= 0):
- raise FeatureLibError("Character value must be between "
- "{:#x} and {:#x}".format(0, 0xFFFFFF),
- location)
+ raise FeatureLibError(
+ "Character value must be between "
+ "{:#x} and {:#x}".format(0, 0xFFFFFF),
+ location,
+ )
return self.ast.CharacterStatement(character, tag, location=location)
def parse_FontRevision_(self):
+ # Parses a ``FontRevision`` statement found in the head table. See
+ # `section 9.c <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.c>`_.
assert self.cur_token_ == "FontRevision", self.cur_token_
location, version = self.cur_token_location_, self.expect_float_()
self.expect_symbol_(";")
if version <= 0:
- raise FeatureLibError("Font revision numbers must be positive",
- location)
+ raise FeatureLibError("Font revision numbers must be positive", location)
return self.ast.FontRevisionStatement(version, location=location)
- def parse_block_(self, block, vertical, stylisticset=None,
- size_feature=False, cv_feature=None):
+ def parse_block_(
+ self, block, vertical, stylisticset=None, size_feature=False, cv_feature=None
+ ):
self.expect_symbol_("{")
for symtab in self.symbol_tables_:
symtab.enter_scope()
@@ -1379,8 +1827,9 @@ class Parser(object):
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_(comments=True)
if self.cur_token_type_ is Lexer.COMMENT:
- statements.append(self.ast.Comment(
- self.cur_token_, location=self.cur_token_location_))
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
elif self.cur_token_type_ is Lexer.GLYPHCLASS:
statements.append(self.parse_glyphclass_definition_())
elif self.is_cur_keyword_("anchorDef"):
@@ -1401,11 +1850,11 @@ class Parser(object):
statements.append(self.parse_markClass_())
elif self.is_cur_keyword_({"pos", "position"}):
statements.append(
- self.parse_position_(enumerated=False, vertical=vertical))
+ self.parse_position_(enumerated=False, vertical=vertical)
+ )
elif self.is_cur_keyword_("script"):
statements.append(self.parse_script_())
- elif (self.is_cur_keyword_({"sub", "substitute",
- "rsub", "reversesub"})):
+ elif self.is_cur_keyword_({"sub", "substitute", "rsub", "reversesub"}):
statements.append(self.parse_substitute_())
elif self.is_cur_keyword_("subtable"):
statements.append(self.parse_subtable_())
@@ -1419,14 +1868,20 @@ class Parser(object):
statements.append(self.parse_size_parameters_())
elif size_feature and self.is_cur_keyword_("sizemenuname"):
statements.append(self.parse_size_menuname_())
- elif self.cur_token_type_ is Lexer.NAME and self.cur_token_ in self.extensions:
+ elif (
+ self.cur_token_type_ is Lexer.NAME
+ and self.cur_token_ in self.extensions
+ ):
statements.append(self.extensions[self.cur_token_](self))
elif self.cur_token_ == ";":
continue
else:
raise FeatureLibError(
- "Expected glyph class definition or statement: got {} {}".format(self.cur_token_type_, self.cur_token_),
- self.cur_token_location_)
+ "Expected glyph class definition or statement: got {} {}".format(
+ self.cur_token_type_, self.cur_token_
+ ),
+ self.cur_token_location_,
+ )
self.expect_symbol_("}")
for symtab in self.symbol_tables_:
@@ -1434,8 +1889,9 @@ class Parser(object):
name = self.expect_name_()
if name != block.name.strip():
- raise FeatureLibError("Expected \"%s\"" % block.name.strip(),
- self.cur_token_location_)
+ raise FeatureLibError(
+ 'Expected "%s"' % block.name.strip(), self.cur_token_location_
+ )
self.expect_symbol_(";")
# A multiple substitution may have a single destination, in which case
@@ -1454,12 +1910,27 @@ class Parser(object):
# Upgrade all single substitutions to multiple substitutions.
if has_single and has_multiple:
- for i, s in enumerate(statements):
+ statements = []
+ for s in block.statements:
if isinstance(s, self.ast.SingleSubstStatement):
- statements[i] = self.ast.MultipleSubstStatement(
- s.prefix, s.glyphs[0].glyphSet()[0], s.suffix,
- [r.glyphSet()[0] for r in s.replacements],
- s.forceChain, location=s.location)
+ glyphs = s.glyphs[0].glyphSet()
+ replacements = s.replacements[0].glyphSet()
+ if len(replacements) == 1:
+ replacements *= len(glyphs)
+ for i, glyph in enumerate(glyphs):
+ statements.append(
+ self.ast.MultipleSubstStatement(
+ s.prefix,
+ glyph,
+ s.suffix,
+ [replacements[i]],
+ s.forceChain,
+ location=s.location,
+ )
+ )
+ else:
+ statements.append(s)
+ block.statements = statements
def is_cur_keyword_(self, k):
if self.cur_token_type_ is Lexer.NAME:
@@ -1484,8 +1955,7 @@ class Parser(object):
def expect_filename_(self):
self.advance_lexer_()
if self.cur_token_type_ is not Lexer.FILENAME:
- raise FeatureLibError("Expected file name",
- self.cur_token_location_)
+ raise FeatureLibError("Expected file name", self.cur_token_location_)
return self.cur_token_
def expect_glyph_(self):
@@ -1495,22 +1965,39 @@ class Parser(object):
if len(self.cur_token_) > 63:
raise FeatureLibError(
"Glyph names must not be longer than 63 characters",
- self.cur_token_location_)
+ self.cur_token_location_,
+ )
return self.cur_token_
elif self.cur_token_type_ is Lexer.CID:
return "cid%05d" % self.cur_token_
- raise FeatureLibError("Expected a glyph name or CID",
- self.cur_token_location_)
+ raise FeatureLibError("Expected a glyph name or CID", self.cur_token_location_)
+
+ def check_glyph_name_in_glyph_set(self, *names):
+ """Raises if glyph name (just `start`) or glyph names of a
+ range (`start` and `end`) are not in the glyph set.
+
+ If no glyph set is present, does nothing.
+ """
+ if self.glyphNames_:
+ missing = [name for name in names if name not in self.glyphNames_]
+ if missing:
+ raise FeatureLibError(
+ "The following glyph names are referenced but are missing from the "
+ f"glyph set: {', '.join(missing)}",
+ self.cur_token_location_,
+ )
def expect_markClass_reference_(self):
name = self.expect_class_name_()
mc = self.glyphclasses_.resolve(name)
if mc is None:
- raise FeatureLibError("Unknown markClass @%s" % name,
- self.cur_token_location_)
+ raise FeatureLibError(
+ "Unknown markClass @%s" % name, self.cur_token_location_
+ )
if not isinstance(mc, self.ast.MarkClass):
- raise FeatureLibError("@%s is not a markClass" % name,
- self.cur_token_location_)
+ raise FeatureLibError(
+ "@%s is not a markClass" % name, self.cur_token_location_
+ )
return mc
def expect_tag_(self):
@@ -1518,8 +2005,9 @@ class Parser(object):
if self.cur_token_type_ is not Lexer.NAME:
raise FeatureLibError("Expected a tag", self.cur_token_location_)
if len(self.cur_token_) > 4:
- raise FeatureLibError("Tags can not be longer than 4 characters",
- self.cur_token_location_)
+ raise FeatureLibError(
+ "Tags cannot be longer than 4 characters", self.cur_token_location_
+ )
return (self.cur_token_ + " ")[:4]
def expect_script_tag_(self):
@@ -1527,7 +2015,8 @@ class Parser(object):
if tag == "dflt":
raise FeatureLibError(
'"dflt" is not a valid script tag; use "DFLT" instead',
- self.cur_token_location_)
+ self.cur_token_location_,
+ )
return tag
def expect_language_tag_(self):
@@ -1535,22 +2024,21 @@ class Parser(object):
if tag == "DFLT":
raise FeatureLibError(
'"DFLT" is not a valid language tag; use "dflt" instead',
- self.cur_token_location_)
+ self.cur_token_location_,
+ )
return tag
def expect_symbol_(self, symbol):
self.advance_lexer_()
if self.cur_token_type_ is Lexer.SYMBOL and self.cur_token_ == symbol:
return symbol
- raise FeatureLibError("Expected '%s'" % symbol,
- self.cur_token_location_)
+ raise FeatureLibError("Expected '%s'" % symbol, self.cur_token_location_)
def expect_keyword_(self, keyword):
self.advance_lexer_()
if self.cur_token_type_ is Lexer.NAME and self.cur_token_ == keyword:
return self.cur_token_
- raise FeatureLibError("Expected \"%s\"" % keyword,
- self.cur_token_location_)
+ raise FeatureLibError('Expected "%s"' % keyword, self.cur_token_location_)
def expect_name_(self):
self.advance_lexer_()
@@ -1558,41 +2046,63 @@ class Parser(object):
return self.cur_token_
raise FeatureLibError("Expected a name", self.cur_token_location_)
- # TODO: Don't allow this method to accept hexadecimal values
def expect_number_(self):
self.advance_lexer_()
if self.cur_token_type_ is Lexer.NUMBER:
return self.cur_token_
raise FeatureLibError("Expected a number", self.cur_token_location_)
+ def expect_any_number_(self):
+ self.advance_lexer_()
+ if self.cur_token_type_ in Lexer.NUMBERS:
+ return self.cur_token_
+ raise FeatureLibError(
+ "Expected a decimal, hexadecimal or octal number", self.cur_token_location_
+ )
+
def expect_float_(self):
self.advance_lexer_()
if self.cur_token_type_ is Lexer.FLOAT:
return self.cur_token_
- raise FeatureLibError("Expected a floating-point number",
- self.cur_token_location_)
+ raise FeatureLibError(
+ "Expected a floating-point number", self.cur_token_location_
+ )
- # TODO: Don't allow this method to accept hexadecimal values
def expect_decipoint_(self):
if self.next_token_type_ == Lexer.FLOAT:
return self.expect_float_()
elif self.next_token_type_ is Lexer.NUMBER:
return self.expect_number_() / 10
else:
- raise FeatureLibError("Expected an integer or floating-point number",
- self.cur_token_location_)
-
- def expect_decimal_or_hexadecimal_(self):
- # the lexer returns the same token type 'NUMBER' for either decimal or
- # hexadecimal integers, and casts them both to a `int` type, so it's
- # impossible to distinguish the two here. This method is implemented
- # the same as `expect_number_`, only it gives a more informative
- # error message
- self.advance_lexer_()
- if self.cur_token_type_ is Lexer.NUMBER:
- return self.cur_token_
- raise FeatureLibError("Expected a decimal or hexadecimal number",
- self.cur_token_location_)
+ raise FeatureLibError(
+ "Expected an integer or floating-point number", self.cur_token_location_
+ )
+
+ def expect_stat_flags(self):
+ value = 0
+ flags = {
+ "OlderSiblingFontAttribute": 1,
+ "ElidableAxisValueName": 2,
+ }
+ while self.next_token_ != ";":
+ if self.next_token_ in flags:
+ name = self.expect_name_()
+ value = value | flags[name]
+ else:
+ raise FeatureLibError(
+ f"Unexpected STAT flag {self.cur_token_}", self.cur_token_location_
+ )
+ return value
+
+ def expect_stat_values_(self):
+ if self.next_token_type_ == Lexer.FLOAT:
+ return self.expect_float_()
+ elif self.next_token_type_ is Lexer.NUMBER:
+ return self.expect_number_()
+ else:
+ raise FeatureLibError(
+ "Expected an integer or floating-point number", self.cur_token_location_
+ )
def expect_string_(self):
self.advance_lexer_()
@@ -1607,11 +2117,17 @@ class Parser(object):
return
else:
self.cur_token_type_, self.cur_token_, self.cur_token_location_ = (
- self.next_token_type_, self.next_token_, self.next_token_location_)
+ self.next_token_type_,
+ self.next_token_,
+ self.next_token_location_,
+ )
while True:
try:
- (self.next_token_type_, self.next_token_,
- self.next_token_location_) = next(self.lexer_)
+ (
+ self.next_token_type_,
+ self.next_token_,
+ self.next_token_location_,
+ ) = next(self.lexer_)
except StopIteration:
self.next_token_type_, self.next_token_ = (None, None)
if self.next_token_type_ != Lexer.COMMENT:
@@ -1621,14 +2137,15 @@ class Parser(object):
@staticmethod
def reverse_string_(s):
"""'abc' --> 'cba'"""
- return ''.join(reversed(list(s)))
+ return "".join(reversed(list(s)))
def make_cid_range_(self, location, start, limit):
"""(location, 999, 1001) --> ["cid00999", "cid01000", "cid01001"]"""
result = list()
if start > limit:
raise FeatureLibError(
- "Bad range: start should be less than limit", location)
+ "Bad range: start should be less than limit", location
+ )
for cid in range(start, limit + 1):
result.append("cid%05d" % cid)
return result
@@ -1638,45 +2155,45 @@ class Parser(object):
result = list()
if len(start) != len(limit):
raise FeatureLibError(
- "Bad range: \"%s\" and \"%s\" should have the same length" %
- (start, limit), location)
+ 'Bad range: "%s" and "%s" should have the same length' % (start, limit),
+ location,
+ )
rev = self.reverse_string_
prefix = os.path.commonprefix([start, limit])
suffix = rev(os.path.commonprefix([rev(start), rev(limit)]))
if len(suffix) > 0:
- start_range = start[len(prefix):-len(suffix)]
- limit_range = limit[len(prefix):-len(suffix)]
+ start_range = start[len(prefix) : -len(suffix)]
+ limit_range = limit[len(prefix) : -len(suffix)]
else:
- start_range = start[len(prefix):]
- limit_range = limit[len(prefix):]
+ start_range = start[len(prefix) :]
+ limit_range = limit[len(prefix) :]
if start_range >= limit_range:
raise FeatureLibError(
- "Start of range must be smaller than its end",
- location)
+ "Start of range must be smaller than its end", location
+ )
- uppercase = re.compile(r'^[A-Z]$')
+ uppercase = re.compile(r"^[A-Z]$")
if uppercase.match(start_range) and uppercase.match(limit_range):
for c in range(ord(start_range), ord(limit_range) + 1):
result.append("%s%c%s" % (prefix, c, suffix))
return result
- lowercase = re.compile(r'^[a-z]$')
+ lowercase = re.compile(r"^[a-z]$")
if lowercase.match(start_range) and lowercase.match(limit_range):
for c in range(ord(start_range), ord(limit_range) + 1):
result.append("%s%c%s" % (prefix, c, suffix))
return result
- digits = re.compile(r'^[0-9]{1,3}$')
+ digits = re.compile(r"^[0-9]{1,3}$")
if digits.match(start_range) and digits.match(limit_range):
for i in range(int(start_range, 10), int(limit_range, 10) + 1):
- number = ("000" + str(i))[-len(start_range):]
+ number = ("000" + str(i))[-len(start_range) :]
result.append("%s%s%s" % (prefix, number, suffix))
return result
- raise FeatureLibError("Bad range: \"%s-%s\"" % (start, limit),
- location)
+ raise FeatureLibError('Bad range: "%s-%s"' % (start, limit), location)
class SymbolTable(object):
diff --git a/Lib/fontTools/fontBuilder.py b/Lib/fontTools/fontBuilder.py
index c6811f05..e2824084 100644
--- a/Lib/fontTools/fontBuilder.py
+++ b/Lib/fontTools/fontBuilder.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-
__all__ = ["FontBuilder"]
"""
@@ -132,198 +129,196 @@ fb.save("test.otf")
```
"""
-from .misc.py23 import *
from .ttLib import TTFont, newTable
from .ttLib.tables._c_m_a_p import cmap_classes
-from .ttLib.tables._n_a_m_e import NameRecord, makeName
from .misc.timeTools import timestampNow
import struct
+from collections import OrderedDict
_headDefaults = dict(
- tableVersion = 1.0,
- fontRevision = 1.0,
- checkSumAdjustment = 0,
- magicNumber = 0x5F0F3CF5,
- flags = 0x0003,
- unitsPerEm = 1000,
- created = 0,
- modified = 0,
- xMin = 0,
- yMin = 0,
- xMax = 0,
- yMax = 0,
- macStyle = 0,
- lowestRecPPEM = 3,
- fontDirectionHint = 2,
- indexToLocFormat = 0,
- glyphDataFormat = 0,
+ tableVersion=1.0,
+ fontRevision=1.0,
+ checkSumAdjustment=0,
+ magicNumber=0x5F0F3CF5,
+ flags=0x0003,
+ unitsPerEm=1000,
+ created=0,
+ modified=0,
+ xMin=0,
+ yMin=0,
+ xMax=0,
+ yMax=0,
+ macStyle=0,
+ lowestRecPPEM=3,
+ fontDirectionHint=2,
+ indexToLocFormat=0,
+ glyphDataFormat=0,
)
_maxpDefaultsTTF = dict(
- tableVersion = 0x00010000,
- numGlyphs = 0,
- maxPoints = 0,
- maxContours = 0,
- maxCompositePoints = 0,
- maxCompositeContours = 0,
- maxZones = 2,
- maxTwilightPoints = 0,
- maxStorage = 0,
- maxFunctionDefs = 0,
- maxInstructionDefs = 0,
- maxStackElements = 0,
- maxSizeOfInstructions = 0,
- maxComponentElements = 0,
- maxComponentDepth = 0,
+ tableVersion=0x00010000,
+ numGlyphs=0,
+ maxPoints=0,
+ maxContours=0,
+ maxCompositePoints=0,
+ maxCompositeContours=0,
+ maxZones=2,
+ maxTwilightPoints=0,
+ maxStorage=0,
+ maxFunctionDefs=0,
+ maxInstructionDefs=0,
+ maxStackElements=0,
+ maxSizeOfInstructions=0,
+ maxComponentElements=0,
+ maxComponentDepth=0,
)
_maxpDefaultsOTF = dict(
- tableVersion = 0x00005000,
- numGlyphs = 0,
+ tableVersion=0x00005000,
+ numGlyphs=0,
)
_postDefaults = dict(
- formatType = 3.0,
- italicAngle = 0,
- underlinePosition = 0,
- underlineThickness = 0,
- isFixedPitch = 0,
- minMemType42 = 0,
- maxMemType42 = 0,
- minMemType1 = 0,
- maxMemType1 = 0,
+ formatType=3.0,
+ italicAngle=0,
+ underlinePosition=0,
+ underlineThickness=0,
+ isFixedPitch=0,
+ minMemType42=0,
+ maxMemType42=0,
+ minMemType1=0,
+ maxMemType1=0,
)
_hheaDefaults = dict(
- tableVersion = 0x00010000,
- ascent = 0,
- descent = 0,
- lineGap = 0,
- advanceWidthMax = 0,
- minLeftSideBearing = 0,
- minRightSideBearing = 0,
- xMaxExtent = 0,
- caretSlopeRise = 1,
- caretSlopeRun = 0,
- caretOffset = 0,
- reserved0 = 0,
- reserved1 = 0,
- reserved2 = 0,
- reserved3 = 0,
- metricDataFormat = 0,
- numberOfHMetrics = 0,
+ tableVersion=0x00010000,
+ ascent=0,
+ descent=0,
+ lineGap=0,
+ advanceWidthMax=0,
+ minLeftSideBearing=0,
+ minRightSideBearing=0,
+ xMaxExtent=0,
+ caretSlopeRise=1,
+ caretSlopeRun=0,
+ caretOffset=0,
+ reserved0=0,
+ reserved1=0,
+ reserved2=0,
+ reserved3=0,
+ metricDataFormat=0,
+ numberOfHMetrics=0,
)
_vheaDefaults = dict(
- tableVersion = 0x00010000,
- ascent = 0,
- descent = 0,
- lineGap = 0,
- advanceHeightMax = 0,
- minTopSideBearing = 0,
- minBottomSideBearing = 0,
- yMaxExtent = 0,
- caretSlopeRise = 0,
- caretSlopeRun = 0,
- reserved0 = 0,
- reserved1 = 0,
- reserved2 = 0,
- reserved3 = 0,
- reserved4 = 0,
- metricDataFormat = 0,
- numberOfVMetrics = 0,
+ tableVersion=0x00010000,
+ ascent=0,
+ descent=0,
+ lineGap=0,
+ advanceHeightMax=0,
+ minTopSideBearing=0,
+ minBottomSideBearing=0,
+ yMaxExtent=0,
+ caretSlopeRise=0,
+ caretSlopeRun=0,
+ reserved0=0,
+ reserved1=0,
+ reserved2=0,
+ reserved3=0,
+ reserved4=0,
+ metricDataFormat=0,
+ numberOfVMetrics=0,
)
_nameIDs = dict(
- copyright = 0,
- familyName = 1,
- styleName = 2,
- uniqueFontIdentifier = 3,
- fullName = 4,
- version = 5,
- psName = 6,
- trademark = 7,
- manufacturer = 8,
- designer = 9,
- description = 10,
- vendorURL = 11,
- designerURL = 12,
- licenseDescription = 13,
- licenseInfoURL = 14,
- # reserved = 15,
- typographicFamily = 16,
- typographicSubfamily = 17,
- compatibleFullName = 18,
- sampleText = 19,
- postScriptCIDFindfontName = 20,
- wwsFamilyName = 21,
- wwsSubfamilyName = 22,
- lightBackgroundPalette = 23,
- darkBackgroundPalette = 24,
- variationsPostScriptNamePrefix = 25,
+ copyright=0,
+ familyName=1,
+ styleName=2,
+ uniqueFontIdentifier=3,
+ fullName=4,
+ version=5,
+ psName=6,
+ trademark=7,
+ manufacturer=8,
+ designer=9,
+ description=10,
+ vendorURL=11,
+ designerURL=12,
+ licenseDescription=13,
+ licenseInfoURL=14,
+ # reserved = 15,
+ typographicFamily=16,
+ typographicSubfamily=17,
+ compatibleFullName=18,
+ sampleText=19,
+ postScriptCIDFindfontName=20,
+ wwsFamilyName=21,
+ wwsSubfamilyName=22,
+ lightBackgroundPalette=23,
+ darkBackgroundPalette=24,
+ variationsPostScriptNamePrefix=25,
)
# to insert in setupNameTable doc string:
# print("\n".join(("%s (nameID %s)" % (k, v)) for k, v in sorted(_nameIDs.items(), key=lambda x: x[1])))
_panoseDefaults = dict(
- bFamilyType = 0,
- bSerifStyle = 0,
- bWeight = 0,
- bProportion = 0,
- bContrast = 0,
- bStrokeVariation = 0,
- bArmStyle = 0,
- bLetterForm = 0,
- bMidline = 0,
- bXHeight = 0,
+ bFamilyType=0,
+ bSerifStyle=0,
+ bWeight=0,
+ bProportion=0,
+ bContrast=0,
+ bStrokeVariation=0,
+ bArmStyle=0,
+ bLetterForm=0,
+ bMidline=0,
+ bXHeight=0,
)
_OS2Defaults = dict(
- version = 3,
- xAvgCharWidth = 0,
- usWeightClass = 400,
- usWidthClass = 5,
- fsType = 0x0004, # default: Preview & Print embedding
- ySubscriptXSize = 0,
- ySubscriptYSize = 0,
- ySubscriptXOffset = 0,
- ySubscriptYOffset = 0,
- ySuperscriptXSize = 0,
- ySuperscriptYSize = 0,
- ySuperscriptXOffset = 0,
- ySuperscriptYOffset = 0,
- yStrikeoutSize = 0,
- yStrikeoutPosition = 0,
- sFamilyClass = 0,
- panose = _panoseDefaults,
- ulUnicodeRange1 = 0,
- ulUnicodeRange2 = 0,
- ulUnicodeRange3 = 0,
- ulUnicodeRange4 = 0,
- achVendID = "????",
- fsSelection = 0,
- usFirstCharIndex = 0,
- usLastCharIndex = 0,
- sTypoAscender = 0,
- sTypoDescender = 0,
- sTypoLineGap = 0,
- usWinAscent = 0,
- usWinDescent = 0,
- ulCodePageRange1 = 0,
- ulCodePageRange2 = 0,
- sxHeight = 0,
- sCapHeight = 0,
- usDefaultChar = 0, # .notdef
- usBreakChar = 32, # space
- usMaxContext = 0,
- usLowerOpticalPointSize = 0,
- usUpperOpticalPointSize = 0,
+ version=3,
+ xAvgCharWidth=0,
+ usWeightClass=400,
+ usWidthClass=5,
+ fsType=0x0004, # default: Preview & Print embedding
+ ySubscriptXSize=0,
+ ySubscriptYSize=0,
+ ySubscriptXOffset=0,
+ ySubscriptYOffset=0,
+ ySuperscriptXSize=0,
+ ySuperscriptYSize=0,
+ ySuperscriptXOffset=0,
+ ySuperscriptYOffset=0,
+ yStrikeoutSize=0,
+ yStrikeoutPosition=0,
+ sFamilyClass=0,
+ panose=_panoseDefaults,
+ ulUnicodeRange1=0,
+ ulUnicodeRange2=0,
+ ulUnicodeRange3=0,
+ ulUnicodeRange4=0,
+ achVendID="????",
+ fsSelection=0,
+ usFirstCharIndex=0,
+ usLastCharIndex=0,
+ sTypoAscender=0,
+ sTypoDescender=0,
+ sTypoLineGap=0,
+ usWinAscent=0,
+ usWinDescent=0,
+ ulCodePageRange1=0,
+ ulCodePageRange2=0,
+ sxHeight=0,
+ sCapHeight=0,
+ usDefaultChar=0, # .notdef
+ usBreakChar=32, # space
+ usMaxContext=0,
+ usLowerOpticalPointSize=0,
+ usUpperOpticalPointSize=0,
)
class FontBuilder(object):
-
def __init__(self, unitsPerEm=None, font=None, isTTF=True):
"""Initialize a FontBuilder instance.
@@ -397,7 +392,7 @@ class FontBuilder(object):
"""
subTables = []
highestUnicode = max(cmapping)
- if highestUnicode > 0xffff:
+ if highestUnicode > 0xFFFF:
cmapping_3_1 = dict((k, v) for k, v in cmapping.items() if k < 0x10000)
subTable_3_10 = buildCmapSubTable(cmapping, 12, 3, 10)
subTables.append(subTable_3_10)
@@ -410,7 +405,9 @@ class FontBuilder(object):
except struct.error:
# format 4 overflowed, fall back to format 12
if not allowFallback:
- raise ValueError("cmap format 4 subtable overflowed; sort glyph order by unicode to fix.")
+ raise ValueError(
+ "cmap format 4 subtable overflowed; sort glyph order by unicode to fix."
+ )
format = 12
subTable_3_1 = buildCmapSubTable(cmapping_3_1, format, 3, 1)
subTables.append(subTable_3_1)
@@ -479,7 +476,7 @@ class FontBuilder(object):
nameID = nameName
else:
nameID = _nameIDs[nameName]
- if isinstance(nameValue, basestring):
+ if isinstance(nameValue, str):
nameValue = dict(en=nameValue)
nameTable.addMultilingualName(
nameValue, ttFont=self.font, nameID=nameID, windows=windows, mac=mac
@@ -491,23 +488,40 @@ class FontBuilder(object):
"""
if "xAvgCharWidth" not in values:
gs = self.font.getGlyphSet()
- widths = [gs[glyphName].width for glyphName in gs.keys() if gs[glyphName].width > 0]
+ widths = [
+ gs[glyphName].width
+ for glyphName in gs.keys()
+ if gs[glyphName].width > 0
+ ]
values["xAvgCharWidth"] = int(round(sum(widths) / float(len(widths))))
self._initTableWithValues("OS/2", _OS2Defaults, values)
- if not ("ulUnicodeRange1" in values or "ulUnicodeRange2" in values or
- "ulUnicodeRange3" in values or "ulUnicodeRange3" in values):
- assert "cmap" in self.font, "the 'cmap' table must be setup before the 'OS/2' table"
+ if not (
+ "ulUnicodeRange1" in values
+ or "ulUnicodeRange2" in values
+ or "ulUnicodeRange3" in values
+ or "ulUnicodeRange3" in values
+ ):
+ assert (
+ "cmap" in self.font
+ ), "the 'cmap' table must be setup before the 'OS/2' table"
self.font["OS/2"].recalcUnicodeRanges(self.font)
def setupCFF(self, psName, fontInfo, charStringsDict, privateDict):
- from .cffLib import CFFFontSet, TopDictIndex, TopDict, CharStrings, \
- GlobalSubrsIndex, PrivateDict
+ from .cffLib import (
+ CFFFontSet,
+ TopDictIndex,
+ TopDict,
+ CharStrings,
+ GlobalSubrsIndex,
+ PrivateDict,
+ )
assert not self.isTTF
self.font.sfntVersion = "OTTO"
fontSet = CFFFontSet()
fontSet.major = 1
fontSet.minor = 0
+ fontSet.otFont = self.font
fontSet.fontNames = [psName]
fontSet.topDictIndex = TopDictIndex()
@@ -522,13 +536,16 @@ class FontBuilder(object):
topDict = TopDict()
topDict.charset = self.font.getGlyphOrder()
topDict.Private = private
+ topDict.GlobalSubrs = fontSet.GlobalSubrs
for key, value in fontInfo.items():
setattr(topDict, key, value)
if "FontMatrix" not in fontInfo:
scale = 1 / self.font["head"].unitsPerEm
topDict.FontMatrix = [scale, 0, 0, scale, 0, 0]
- charStrings = CharStrings(None, topDict.charset, globalSubrs, private, fdSelect, fdArray)
+ charStrings = CharStrings(
+ None, topDict.charset, globalSubrs, private, fdSelect, fdArray
+ )
for glyphName, charString in charStringsDict.items():
charString.private = private
charString.globalSubrs = globalSubrs
@@ -541,8 +558,16 @@ class FontBuilder(object):
self.font["CFF "].cff = fontSet
def setupCFF2(self, charStringsDict, fdArrayList=None, regions=None):
- from .cffLib import CFFFontSet, TopDictIndex, TopDict, CharStrings, \
- GlobalSubrsIndex, PrivateDict, FDArrayIndex, FontDict
+ from .cffLib import (
+ CFFFontSet,
+ TopDictIndex,
+ TopDict,
+ CharStrings,
+ GlobalSubrsIndex,
+ PrivateDict,
+ FDArrayIndex,
+ FontDict,
+ )
assert not self.isTTF
self.font.sfntVersion = "OTTO"
@@ -604,7 +629,10 @@ class FontBuilder(object):
varData = buildVarData(list(range(len(regions))), None, optimize=False)
varStore = buildVarStore(varRegionList, [varData])
vstore = VarStoreData(otVarStore=varStore)
- self.font["CFF2"].cff.topDictIndex[0].VarStore = vstore
+ topDict = self.font["CFF2"].cff.topDictIndex[0]
+ topDict.VarStore = vstore
+ for fontDict in topDict.FDArray:
+ fontDict.Private.vstore = vstore
def setupGlyf(self, glyphs, calcGlyphBounds=True):
"""Create the `glyf` table from a dict, that maps glyph names
@@ -625,10 +653,40 @@ class FontBuilder(object):
self.calcGlyphBounds()
def setupFvar(self, axes, instances):
+ """Adds an font variations table to the font.
+
+ Args:
+ axes (list): See below.
+ instances (list): See below.
+
+ ``axes`` should be a list of axes, with each axis either supplied as
+ a py:class:`.designspaceLib.AxisDescriptor` object, or a tuple in the
+ format ```tupletag, minValue, defaultValue, maxValue, name``.
+ The ``name`` is either a string, or a dict, mapping language codes
+ to strings, to allow localized name table entries.
+
+ ```instances`` should be a list of instances, with each instance either
+ supplied as a py:class:`.designspaceLib.InstanceDescriptor` object, or a
+ dict with keys ``location`` (mapping of axis tags to float values),
+ ``stylename`` and (optionally) ``postscriptfontname``.
+ The ``stylename`` is either a string, or a dict, mapping language codes
+ to strings, to allow localized name table entries.
+ """
+
addFvar(self.font, axes, instances)
+ def setupAvar(self, axes):
+ """Adds an axis variations table to the font.
+
+ Args:
+ axes (list): A list of py:class:`.designspaceLib.AxisDescriptor` objects.
+ """
+ from .varLib import _add_avar
+
+ _add_avar(self.font, OrderedDict(enumerate(axes))) # Only values are used
+
def setupGvar(self, variations):
- gvar = self.font["gvar"] = newTable('gvar')
+ gvar = self.font["gvar"] = newTable("gvar")
gvar.version = 1
gvar.reserved = 0
gvar.variations = variations
@@ -647,7 +705,7 @@ class FontBuilder(object):
The `metrics` argument must be a dict, mapping glyph names to
`(width, leftSidebearing)` tuples.
"""
- self.setupMetrics('hmtx', metrics)
+ self.setupMetrics("hmtx", metrics)
def setupVerticalMetrics(self, metrics):
"""Create a new `vmtx` table, for horizontal metrics.
@@ -655,7 +713,7 @@ class FontBuilder(object):
The `metrics` argument must be a dict, mapping glyph names to
`(height, topSidebearing)` tuples.
"""
- self.setupMetrics('vmtx', metrics)
+ self.setupMetrics("vmtx", metrics)
def setupMetrics(self, tableTag, metrics):
"""See `setupHorizontalMetrics()` and `setupVerticalMetrics()`."""
@@ -696,8 +754,14 @@ class FontBuilder(object):
bag[vorg] = 1
else:
bag[vorg] += 1
- defaultVerticalOrigin = sorted(bag, key=lambda vorg: bag[vorg], reverse=True)[0]
- self._initTableWithValues("VORG", {}, dict(VOriginRecords={}, defaultVertOriginY=defaultVerticalOrigin))
+ defaultVerticalOrigin = sorted(
+ bag, key=lambda vorg: bag[vorg], reverse=True
+ )[0]
+ self._initTableWithValues(
+ "VORG",
+ {},
+ dict(VOriginRecords={}, defaultVertOriginY=defaultVerticalOrigin),
+ )
vorgTable = self.font["VORG"]
vorgTable.majorVersion = 1
vorgTable.minorVersion = 0
@@ -708,7 +772,7 @@ class FontBuilder(object):
"""Create a new `post` table and initialize it with default values,
which can be overridden by keyword arguments.
"""
- isCFF2 = 'CFF2' in self.font
+ isCFF2 = "CFF2" in self.font
postTable = self._initTableWithValues("post", _postDefaults, values)
if (self.isTTF or isCFF2) and keepGlyphNames:
postTable.formatType = 2.0
@@ -732,10 +796,10 @@ class FontBuilder(object):
happy. This does not properly sign the font.
"""
values = dict(
- ulVersion = 1,
- usFlag = 0,
- usNumSigs = 0,
- signatureRecords = [],
+ ulVersion=1,
+ usFlag=0,
+ usNumSigs=0,
+ signatureRecords=[],
)
self._initTableWithValues("DSIG", {}, values)
@@ -751,7 +815,70 @@ class FontBuilder(object):
`fontTools.feaLib` for details.
"""
from .feaLib.builder import addOpenTypeFeaturesFromString
- addOpenTypeFeaturesFromString(self.font, features, filename=filename, tables=tables)
+
+ addOpenTypeFeaturesFromString(
+ self.font, features, filename=filename, tables=tables
+ )
+
+ def addFeatureVariations(self, conditionalSubstitutions, featureTag="rvrn"):
+ """Add conditional substitutions to a Variable Font.
+
+ See `fontTools.varLib.featureVars.addFeatureVariations`.
+ """
+ from .varLib import featureVars
+
+ if "fvar" not in self.font:
+ raise KeyError("'fvar' table is missing; can't add FeatureVariations.")
+
+ featureVars.addFeatureVariations(
+ self.font, conditionalSubstitutions, featureTag=featureTag
+ )
+
+ def setupCOLR(self, colorLayers, version=None, varStore=None):
+ """Build new COLR table using color layers dictionary.
+
+ Cf. `fontTools.colorLib.builder.buildCOLR`.
+ """
+ from fontTools.colorLib.builder import buildCOLR
+
+ glyphMap = self.font.getReverseGlyphMap()
+ self.font["COLR"] = buildCOLR(
+ colorLayers, version=version, glyphMap=glyphMap, varStore=varStore
+ )
+
+ def setupCPAL(
+ self,
+ palettes,
+ paletteTypes=None,
+ paletteLabels=None,
+ paletteEntryLabels=None,
+ ):
+ """Build new CPAL table using list of palettes.
+
+ Optionally build CPAL v1 table using paletteTypes, paletteLabels and
+ paletteEntryLabels.
+
+ Cf. `fontTools.colorLib.builder.buildCPAL`.
+ """
+ from fontTools.colorLib.builder import buildCPAL
+
+ self.font["CPAL"] = buildCPAL(
+ palettes,
+ paletteTypes=paletteTypes,
+ paletteLabels=paletteLabels,
+ paletteEntryLabels=paletteEntryLabels,
+ nameTable=self.font.get("name"),
+ )
+
+ def setupStat(self, axes, locations=None, elidedFallbackName=2):
+ """Build a new 'STAT' table.
+
+ See `fontTools.otlLib.builder.buildStatTable` for details about
+ the arguments.
+ """
+ from .otlLib.builder import buildStatTable
+
+ buildStatTable(self.font, axes, locations, elidedFallbackName)
def buildCmapSubTable(cmapping, format, platformID, platEncID):
@@ -764,32 +891,57 @@ def buildCmapSubTable(cmapping, format, platformID, platEncID):
def addFvar(font, axes, instances):
- from .misc.py23 import Tag, tounicode
from .ttLib.tables._f_v_a_r import Axis, NamedInstance
assert axes
- fvar = newTable('fvar')
- nameTable = font['name']
+ fvar = newTable("fvar")
+ nameTable = font["name"]
- for tag, minValue, defaultValue, maxValue, name in axes:
+ for axis_def in axes:
axis = Axis()
- axis.axisTag = Tag(tag)
- axis.minValue, axis.defaultValue, axis.maxValue = minValue, defaultValue, maxValue
- axis.axisNameID = nameTable.addName(tounicode(name))
+
+ if isinstance(axis_def, tuple):
+ (
+ axis.axisTag,
+ axis.minValue,
+ axis.defaultValue,
+ axis.maxValue,
+ name,
+ ) = axis_def
+ else:
+ (axis.axisTag, axis.minValue, axis.defaultValue, axis.maxValue, name) = (
+ axis_def.tag,
+ axis_def.minimum,
+ axis_def.default,
+ axis_def.maximum,
+ axis_def.name,
+ )
+
+ if isinstance(name, str):
+ name = dict(en=name)
+
+ axis.axisNameID = nameTable.addMultilingualName(name, ttFont=font)
fvar.axes.append(axis)
for instance in instances:
- coordinates = instance['location']
- name = tounicode(instance['stylename'])
- psname = instance.get('postscriptfontname')
+ if isinstance(instance, dict):
+ coordinates = instance["location"]
+ name = instance["stylename"]
+ psname = instance.get("postscriptfontname")
+ else:
+ coordinates = instance.location
+ name = instance.localisedStyleName or instance.styleName
+ psname = instance.postScriptFontName
+
+ if isinstance(name, str):
+ name = dict(en=name)
inst = NamedInstance()
- inst.subfamilyNameID = nameTable.addName(name)
+ inst.subfamilyNameID = nameTable.addMultilingualName(name, ttFont=font)
if psname is not None:
- psname = tounicode(psname)
inst.postscriptNameID = nameTable.addName(psname)
inst.coordinates = coordinates
fvar.instances.append(inst)
- font['fvar'] = fvar
+ font["fvar"] = fvar
diff --git a/Lib/fontTools/help.py b/Lib/fontTools/help.py
new file mode 100644
index 00000000..ff8048d5
--- /dev/null
+++ b/Lib/fontTools/help.py
@@ -0,0 +1,34 @@
+import pkgutil
+import sys
+import fontTools
+import importlib
+import os
+from pathlib import Path
+
+
+def main():
+ """Show this help"""
+ path = fontTools.__path__
+ descriptions = {}
+ for pkg in sorted(
+ mod.name
+ for mod in pkgutil.walk_packages([fontTools.__path__[0]], prefix="fontTools.")
+ ):
+ try:
+ imports = __import__(pkg, globals(), locals(), ["main"])
+ except ImportError as e:
+ continue
+ try:
+ description = imports.main.__doc__
+ if description:
+ pkg = pkg.replace("fontTools.", "").replace(".__main__", "")
+ descriptions[pkg] = description
+ except AttributeError as e:
+ pass
+ for pkg, description in descriptions.items():
+ print("fonttools %-12s %s" % (pkg, description), file=sys.stderr)
+
+
+if __name__ == "__main__":
+ print("fonttools v%s\n" % fontTools.__version__, file=sys.stderr)
+ main()
diff --git a/Lib/fontTools/merge.py b/Lib/fontTools/merge.py
index 36ac7565..2df22a8d 100644
--- a/Lib/fontTools/merge.py
+++ b/Lib/fontTools/merge.py
@@ -2,11 +2,6 @@
#
# Google Author(s): Behdad Esfahbod, Roozbeh Pournader
-"""Font merger.
-"""
-
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.timeTools import timestampNow
from fontTools import ttLib, cffLib
from fontTools.ttLib.tables import otTables, _h_e_a_d
@@ -295,11 +290,18 @@ ttLib.getTableClass('OS/2').mergeMap = {
'sTypoLineGap': max,
'usWinAscent': max,
'usWinDescent': max,
- # Version 2,3,4
+ # Version 1
'ulCodePageRange1': onlyExisting(bitwise_or),
'ulCodePageRange2': onlyExisting(bitwise_or),
- 'usMaxContex': onlyExisting(max),
- # TODO version 5
+ # Version 2, 3, 4
+ 'sxHeight': onlyExisting(max),
+ 'sCapHeight': onlyExisting(max),
+ 'usDefaultChar': onlyExisting(first),
+ 'usBreakChar': onlyExisting(first),
+ 'usMaxContext': onlyExisting(max),
+ # version 5
+ 'usLowerOpticalPointSize': onlyExisting(min),
+ 'usUpperOpticalPointSize': onlyExisting(max),
}
@_add_method(ttLib.getTableClass('OS/2'))
@@ -739,12 +741,12 @@ def __merge_classify_context(self):
if self.Format not in [1, 2, 3]:
return None # Don't shoot the messenger; let it go
- if not hasattr(self.__class__, "__ContextHelpers"):
- self.__class__.__ContextHelpers = {}
- if self.Format not in self.__class__.__ContextHelpers:
+ if not hasattr(self.__class__, "_merge__ContextHelpers"):
+ self.__class__._merge__ContextHelpers = {}
+ if self.Format not in self.__class__._merge__ContextHelpers:
helper = ContextHelper(self.__class__, self.Format)
- self.__class__.__ContextHelpers[self.Format] = helper
- return self.__class__.__ContextHelpers[self.Format]
+ self.__class__._merge__ContextHelpers[self.Format] = helper
+ return self.__class__._merge__ContextHelpers[self.Format]
@_add_method(otTables.ContextSubst,
@@ -945,6 +947,34 @@ class _NonhashableDict(object):
del self.d[id(k)]
class Merger(object):
+ """Font merger.
+
+ This class merges multiple files into a single OpenType font, taking into
+ account complexities such as OpenType layout (``GSUB``/``GPOS``) tables and
+ cross-font metrics (e.g. ``hhea.ascent`` is set to the maximum value across
+ all the fonts).
+
+ If multiple glyphs map to the same Unicode value, and the glyphs are considered
+ sufficiently different (that is, they differ in any of paths, widths, or
+ height), then subsequent glyphs are renamed and a lookup in the ``locl``
+ feature will be created to disambiguate them. For example, if the arguments
+ are an Arabic font and a Latin font and both contain a set of parentheses,
+ the Latin glyphs will be renamed to ``parenleft#1`` and ``parenright#1``,
+ and a lookup will be inserted into the to ``locl`` feature (creating it if
+ necessary) under the ``latn`` script to substitute ``parenleft`` with
+ ``parenleft#1`` etc.
+
+ Restrictions:
+
+ - All fonts must currently have TrueType outlines (``glyf`` table).
+ Merging fonts with CFF outlines is not supported.
+ - All fonts must have the same units per em.
+ - If duplicate glyph disambiguation takes place as described above then the
+ fonts must have a ``GSUB`` table.
+
+ Attributes:
+ options: Currently unused.
+ """
def __init__(self, options=None):
@@ -954,7 +984,15 @@ class Merger(object):
self.options = options
def merge(self, fontfiles):
+ """Merges fonts together.
+
+ Args:
+ fontfiles: A list of file names to be merged
+ Returns:
+ A :class:`fontTools.ttLib.TTFont` object. Call the ``save`` method on
+ this to write it out to an OTF file.
+ """
mega = ttLib.TTFont()
#
@@ -975,7 +1013,7 @@ class Merger(object):
self._preMerge(font)
self.fonts = fonts
- self.duplicateGlyphsPerFont = [{} for f in fonts]
+ self.duplicateGlyphsPerFont = [{} for _ in fonts]
allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
allTags.remove('GlyphOrder')
@@ -1013,16 +1051,18 @@ class Merger(object):
def _mergeGlyphOrders(self, glyphOrders):
"""Modifies passed-in glyphOrders to reflect new glyph names.
Returns glyphOrder for the merged font."""
- # Simply append font index to the glyph name for now.
- # TODO Even this simplistic numbering can result in conflicts.
- # But then again, we have to improve this soon anyway.
- mega = []
- for n,glyphOrder in enumerate(glyphOrders):
+ mega = {}
+ for glyphOrder in glyphOrders:
for i,glyphName in enumerate(glyphOrder):
- glyphName += "#" + repr(n)
- glyphOrder[i] = glyphName
- mega.append(glyphName)
- return mega
+ if glyphName in mega:
+ n = mega[glyphName]
+ while (glyphName + "#" + repr(n)) in mega:
+ n += 1
+ mega[glyphName] = n
+ glyphName += "#" + repr(n)
+ glyphOrder[i] = glyphName
+ mega[glyphName] = 1
+ return list(mega.keys())
def mergeObjects(self, returnTable, logic, tables):
# Right now we don't use self at all. Will use in the future
@@ -1135,6 +1175,7 @@ __all__ = [
@timer("make one with everything (TOTAL TIME)")
def main(args=None):
+ """Merge multiple fonts into one"""
from fontTools import configLogger
if args is None:
diff --git a/Lib/fontTools/misc/__init__.py b/Lib/fontTools/misc/__init__.py
index 3f9abc96..156cb232 100644
--- a/Lib/fontTools/misc/__init__.py
+++ b/Lib/fontTools/misc/__init__.py
@@ -1,4 +1 @@
"""Empty __init__.py file to signal Python this directory is a package."""
-
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
diff --git a/Lib/fontTools/misc/arrayTools.py b/Lib/fontTools/misc/arrayTools.py
index ed20230c..c20a9eda 100644
--- a/Lib/fontTools/misc/arrayTools.py
+++ b/Lib/fontTools/misc/arrayTools.py
@@ -1,19 +1,21 @@
-#
-# Various array and rectangle tools, but mostly rectangles, hence the
-# name of this module (not).
-#
+"""Routines for calculating bounding boxes, point in rectangle calculations and
+so on.
+"""
-
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
-from numbers import Number
+from fontTools.misc.roundTools import otRound
+from fontTools.misc.vector import Vector as _Vector
import math
-import operator
+import warnings
+
def calcBounds(array):
- """Return the bounding rectangle of a 2D points array as a tuple:
- (xMin, yMin, xMax, yMax)
+ """Calculate the bounding rectangle of a 2D points array.
+
+ Args:
+ array: A sequence of 2D tuples.
+
+ Returns:
+ A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
"""
if len(array) == 0:
return 0, 0, 0, 0
@@ -22,29 +24,64 @@ def calcBounds(array):
return min(xs), min(ys), max(xs), max(ys)
def calcIntBounds(array, round=otRound):
- """Return the integer bounding rectangle of a 2D points array as a
- tuple: (xMin, yMin, xMax, yMax)
- Values are rounded to closest integer towards +Infinity using otRound
- function by default, unless an optional 'round' function is passed.
+ """Calculate the integer bounding rectangle of a 2D points array.
+
+ Values are rounded to closest integer towards ``+Infinity`` using the
+ :func:`fontTools.misc.fixedTools.otRound` function by default, unless
+ an optional ``round`` function is passed.
+
+ Args:
+ array: A sequence of 2D tuples.
+ round: A rounding function of type ``f(x: float) -> int``.
+
+ Returns:
+ A four-item tuple of integers representing the bounding rectangle:
+ ``(xMin, yMin, xMax, yMax)``.
"""
return tuple(round(v) for v in calcBounds(array))
def updateBounds(bounds, p, min=min, max=max):
- """Return the bounding recangle of rectangle bounds and point (x, y)."""
+ """Add a point to a bounding rectangle.
+
+ Args:
+ bounds: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+ p: A 2D tuple representing a point.
+ min,max: functions to compute the minimum and maximum.
+
+ Returns:
+ The updated bounding rectangle ``(xMin, yMin, xMax, yMax)``.
+ """
(x, y) = p
xMin, yMin, xMax, yMax = bounds
return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
def pointInRect(p, rect):
- """Return True when point (x, y) is inside rect."""
+ """Test if a point is inside a bounding rectangle.
+
+ Args:
+ p: A 2D tuple representing a point.
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ ``True`` if the point is inside the rectangle, ``False`` otherwise.
+ """
(x, y) = p
xMin, yMin, xMax, yMax = rect
return (xMin <= x <= xMax) and (yMin <= y <= yMax)
def pointsInRect(array, rect):
- """Find out which points or array are inside rect.
- Returns an array with a boolean for each point.
+ """Determine which points are inside a bounding rectangle.
+
+ Args:
+ array: A sequence of 2D tuples.
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ A list containing the points inside the rectangle.
"""
if len(array) < 1:
return []
@@ -52,41 +89,105 @@ def pointsInRect(array, rect):
return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array]
def vectorLength(vector):
- """Return the length of the given vector."""
+ """Calculate the length of the given vector.
+
+ Args:
+ vector: A 2D tuple.
+
+ Returns:
+ The Euclidean length of the vector.
+ """
x, y = vector
return math.sqrt(x**2 + y**2)
def asInt16(array):
- """Round and cast to 16 bit integer."""
+ """Round a list of floats to 16-bit signed integers.
+
+ Args:
+ array: List of float values.
+
+ Returns:
+ A list of rounded integers.
+ """
return [int(math.floor(i+0.5)) for i in array]
def normRect(rect):
- """Normalize the rectangle so that the following holds:
+ """Normalize a bounding box rectangle.
+
+ This function "turns the rectangle the right way up", so that the following
+ holds::
+
xMin <= xMax and yMin <= yMax
+
+ Args:
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ A normalized bounding rectangle.
"""
(xMin, yMin, xMax, yMax) = rect
return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
def scaleRect(rect, x, y):
- """Scale the rectangle by x, y."""
+ """Scale a bounding box rectangle.
+
+ Args:
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+ x: Factor to scale the rectangle along the X axis.
+ Y: Factor to scale the rectangle along the Y axis.
+
+ Returns:
+ A scaled bounding rectangle.
+ """
(xMin, yMin, xMax, yMax) = rect
return xMin * x, yMin * y, xMax * x, yMax * y
def offsetRect(rect, dx, dy):
- """Offset the rectangle by dx, dy."""
+ """Offset a bounding box rectangle.
+
+ Args:
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+ dx: Amount to offset the rectangle along the X axis.
+ dY: Amount to offset the rectangle along the Y axis.
+
+ Returns:
+ An offset bounding rectangle.
+ """
(xMin, yMin, xMax, yMax) = rect
return xMin+dx, yMin+dy, xMax+dx, yMax+dy
def insetRect(rect, dx, dy):
- """Inset the rectangle by dx, dy on all sides."""
+ """Inset a bounding box rectangle on all sides.
+
+ Args:
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+ dx: Amount to inset the rectangle along the X axis.
+ dY: Amount to inset the rectangle along the Y axis.
+
+ Returns:
+ An inset bounding rectangle.
+ """
(xMin, yMin, xMax, yMax) = rect
return xMin+dx, yMin+dy, xMax-dx, yMax-dy
def sectRect(rect1, rect2):
- """Return a boolean and a rectangle. If the input rectangles intersect, return
- True and the intersecting rectangle. Return False and (0, 0, 0, 0) if the input
- rectangles don't intersect.
+ """Test for rectangle-rectangle intersection.
+
+ Args:
+ rect1: First bounding rectangle, expressed as tuples
+ ``(xMin, yMin, xMax, yMax)``.
+ rect2: Second bounding rectangle.
+
+ Returns:
+ A boolean and a rectangle.
+ If the input rectangles intersect, returns ``True`` and the intersecting
+ rectangle. Returns ``False`` and ``(0, 0, 0, 0)`` if the input
+ rectangles don't intersect.
"""
(xMin1, yMin1, xMax1, yMax1) = rect1
(xMin2, yMin2, xMax2, yMax2) = rect2
@@ -97,9 +198,16 @@ def sectRect(rect1, rect2):
return True, (xMin, yMin, xMax, yMax)
def unionRect(rect1, rect2):
- """Return the smallest rectangle in which both input rectangles are fully
- enclosed. In other words, return the total bounding rectangle of both input
- rectangles.
+ """Determine union of bounding rectangles.
+
+ Args:
+ rect1: First bounding rectangle, expressed as tuples
+ ``(xMin, yMin, xMax, yMax)``.
+ rect2: Second bounding rectangle.
+
+ Returns:
+ The smallest rectangle in which both input rectangles are fully
+ enclosed.
"""
(xMin1, yMin1, xMax1, yMax1) = rect1
(xMin2, yMin2, xMax2, yMax2) = rect2
@@ -107,16 +215,45 @@ def unionRect(rect1, rect2):
max(xMax1, xMax2), max(yMax1, yMax2))
return (xMin, yMin, xMax, yMax)
-def rectCenter(rect0):
- """Return the center of the rectangle as an (x, y) coordinate."""
- (xMin, yMin, xMax, yMax) = rect0
+def rectCenter(rect):
+ """Determine rectangle center.
+
+ Args:
+ rect: Bounding rectangle, expressed as tuples
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ A 2D tuple representing the point at the center of the rectangle.
+ """
+ (xMin, yMin, xMax, yMax) = rect
return (xMin+xMax)/2, (yMin+yMax)/2
-def intRect(rect1):
- """Return the rectangle, rounded off to integer values, but guaranteeing that
- the resulting rectangle is NOT smaller than the original.
+def rectArea(rect):
+ """Determine rectangle area.
+
+ Args:
+ rect: Bounding rectangle, expressed as tuples
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ The area of the rectangle.
+ """
+ (xMin, yMin, xMax, yMax) = rect
+ return (yMax - yMin) * (xMax - xMin)
+
+def intRect(rect):
+ """Round a rectangle to integer values.
+
+ Guarantees that the resulting rectangle is NOT smaller than the original.
+
+ Args:
+ rect: Bounding rectangle, expressed as tuples
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ A rounded bounding rectangle.
"""
- (xMin, yMin, xMax, yMax) = rect1
+ (xMin, yMin, xMax, yMax) = rect
xMin = int(math.floor(xMin))
yMin = int(math.floor(yMin))
xMax = int(math.ceil(xMax))
@@ -124,121 +261,48 @@ def intRect(rect1):
return (xMin, yMin, xMax, yMax)
-class Vector(object):
- """A math-like vector."""
-
- def __init__(self, values, keep=False):
- self.values = values if keep else list(values)
-
- def __getitem__(self, index):
- return self.values[index]
-
- def __len__(self):
- return len(self.values)
-
- def __repr__(self):
- return "Vector(%s)" % self.values
-
- def _vectorOp(self, other, op):
- if isinstance(other, Vector):
- assert len(self.values) == len(other.values)
- a = self.values
- b = other.values
- return [op(a[i], b[i]) for i in range(len(self.values))]
- if isinstance(other, Number):
- return [op(v, other) for v in self.values]
- raise NotImplementedError
-
- def _scalarOp(self, other, op):
- if isinstance(other, Number):
- return [op(v, other) for v in self.values]
- raise NotImplementedError
-
- def _unaryOp(self, op):
- return [op(v) for v in self.values]
-
- def __add__(self, other):
- return Vector(self._vectorOp(other, operator.add), keep=True)
- def __iadd__(self, other):
- self.values = self._vectorOp(other, operator.add)
- return self
- __radd__ = __add__
-
- def __sub__(self, other):
- return Vector(self._vectorOp(other, operator.sub), keep=True)
- def __isub__(self, other):
- self.values = self._vectorOp(other, operator.sub)
- return self
- def __rsub__(self, other):
- return other + (-self)
-
- def __mul__(self, other):
- return Vector(self._scalarOp(other, operator.mul), keep=True)
- def __imul__(self, other):
- self.values = self._scalarOp(other, operator.mul)
- return self
- __rmul__ = __mul__
-
- def __truediv__(self, other):
- return Vector(self._scalarOp(other, operator.div), keep=True)
- def __itruediv__(self, other):
- self.values = self._scalarOp(other, operator.div)
- return self
-
- def __pos__(self):
- return Vector(self._unaryOp(operator.pos), keep=True)
- def __neg__(self):
- return Vector(self._unaryOp(operator.neg), keep=True)
- def __round__(self):
- return Vector(self._unaryOp(round), keep=True)
- def toInt(self):
- return self.__round__()
-
- def __eq__(self, other):
- if type(other) == Vector:
- return self.values == other.values
- else:
- return self.values == other
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __bool__(self):
- return any(self.values)
- __nonzero__ = __bool__
-
- def __abs__(self):
- return math.sqrt(sum([x*x for x in self.values]))
- def dot(self, other):
- a = self.values
- b = other.values if type(other) == Vector else b
- assert len(a) == len(b)
- return sum([a[i] * b[i] for i in range(len(a))])
+class Vector(_Vector):
+
+ def __init__(self, *args, **kwargs):
+ warnings.warn(
+ "fontTools.misc.arrayTools.Vector has been deprecated, please use "
+ "fontTools.misc.vector.Vector instead.",
+ DeprecationWarning,
+ )
def pairwise(iterable, reverse=False):
- """Iterate over current and next items in iterable, optionally in
- reverse order.
-
- >>> tuple(pairwise([]))
- ()
- >>> tuple(pairwise([], reverse=True))
- ()
- >>> tuple(pairwise([0]))
- ((0, 0),)
- >>> tuple(pairwise([0], reverse=True))
- ((0, 0),)
- >>> tuple(pairwise([0, 1]))
- ((0, 1), (1, 0))
- >>> tuple(pairwise([0, 1], reverse=True))
- ((1, 0), (0, 1))
- >>> tuple(pairwise([0, 1, 2]))
- ((0, 1), (1, 2), (2, 0))
- >>> tuple(pairwise([0, 1, 2], reverse=True))
- ((2, 1), (1, 0), (0, 2))
- >>> tuple(pairwise(['a', 'b', 'c', 'd']))
- (('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'a'))
- >>> tuple(pairwise(['a', 'b', 'c', 'd'], reverse=True))
- (('d', 'c'), ('c', 'b'), ('b', 'a'), ('a', 'd'))
+ """Iterate over current and next items in iterable.
+
+ Args:
+ iterable: An iterable
+ reverse: If true, iterate in reverse order.
+
+ Returns:
+ A iterable yielding two elements per iteration.
+
+ Example:
+
+ >>> tuple(pairwise([]))
+ ()
+ >>> tuple(pairwise([], reverse=True))
+ ()
+ >>> tuple(pairwise([0]))
+ ((0, 0),)
+ >>> tuple(pairwise([0], reverse=True))
+ ((0, 0),)
+ >>> tuple(pairwise([0, 1]))
+ ((0, 1), (1, 0))
+ >>> tuple(pairwise([0, 1], reverse=True))
+ ((1, 0), (0, 1))
+ >>> tuple(pairwise([0, 1, 2]))
+ ((0, 1), (1, 2), (2, 0))
+ >>> tuple(pairwise([0, 1, 2], reverse=True))
+ ((2, 1), (1, 0), (0, 2))
+ >>> tuple(pairwise(['a', 'b', 'c', 'd']))
+ (('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'a'))
+ >>> tuple(pairwise(['a', 'b', 'c', 'd'], reverse=True))
+ (('d', 'c'), ('c', 'b'), ('b', 'a'), ('a', 'd'))
"""
if not iterable:
return
diff --git a/Lib/fontTools/misc/bezierTools.py b/Lib/fontTools/misc/bezierTools.py
index 73053052..2cf2640c 100644
--- a/Lib/fontTools/misc/bezierTools.py
+++ b/Lib/fontTools/misc/bezierTools.py
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
-"""fontTools.misc.bezierTools.py -- tools for working with bezier path segments.
+"""fontTools.misc.bezierTools.py -- tools for working with Bezier path segments.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.arrayTools import calcBounds
-from fontTools.misc.py23 import *
+from fontTools.misc.arrayTools import calcBounds, sectRect, rectArea
+from fontTools.misc.transform import Identity
import math
+from collections import namedtuple
+
+Intersection = namedtuple("Intersection", ["pt", "t1", "t2"])
__all__ = [
@@ -26,32 +28,68 @@ __all__ = [
"splitCubicAtT",
"solveQuadratic",
"solveCubic",
+ "quadraticPointAtT",
+ "cubicPointAtT",
+ "linePointAtT",
+ "segmentPointAtT",
+ "lineLineIntersections",
+ "curveLineIntersections",
+ "curveCurveIntersections",
+ "segmentSegmentIntersections",
]
def calcCubicArcLength(pt1, pt2, pt3, pt4, tolerance=0.005):
- """Return the arc length for a cubic bezier segment."""
- return calcCubicArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4), tolerance)
+ """Calculates the arc length for a cubic Bezier segment.
+
+ Whereas :func:`approximateCubicArcLength` approximates the length, this
+ function calculates it by "measuring", recursively dividing the curve
+ until the divided segments are shorter than ``tolerance``.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
+ tolerance: Controls the precision of the calcuation.
+
+ Returns:
+ Arc length value.
+ """
+ return calcCubicArcLengthC(
+ complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4), tolerance
+ )
def _split_cubic_into_two(p0, p1, p2, p3):
- mid = (p0 + 3 * (p1 + p2) + p3) * .125
- deriv3 = (p3 + p2 - p1 - p0) * .125
- return ((p0, (p0 + p1) * .5, mid - deriv3, mid),
- (mid, mid + deriv3, (p2 + p3) * .5, p3))
+ mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
+ deriv3 = (p3 + p2 - p1 - p0) * 0.125
+ return (
+ (p0, (p0 + p1) * 0.5, mid - deriv3, mid),
+ (mid, mid + deriv3, (p2 + p3) * 0.5, p3),
+ )
+
def _calcCubicArcLengthCRecurse(mult, p0, p1, p2, p3):
- arch = abs(p0-p3)
- box = abs(p0-p1) + abs(p1-p2) + abs(p2-p3)
- if arch * mult >= box:
- return (arch + box) * .5
- else:
- one,two = _split_cubic_into_two(p0,p1,p2,p3)
- return _calcCubicArcLengthCRecurse(mult, *one) + _calcCubicArcLengthCRecurse(mult, *two)
+ arch = abs(p0 - p3)
+ box = abs(p0 - p1) + abs(p1 - p2) + abs(p2 - p3)
+ if arch * mult >= box:
+ return (arch + box) * 0.5
+ else:
+ one, two = _split_cubic_into_two(p0, p1, p2, p3)
+ return _calcCubicArcLengthCRecurse(mult, *one) + _calcCubicArcLengthCRecurse(
+ mult, *two
+ )
+
def calcCubicArcLengthC(pt1, pt2, pt3, pt4, tolerance=0.005):
- """Return the arc length for a cubic bezier segment using complex points."""
- mult = 1. + 1.5 * tolerance # The 1.5 is a empirical hack; no math
+ """Calculates the arc length for a cubic Bezier segment.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.
+ tolerance: Controls the precision of the calcuation.
+
+ Returns:
+ Arc length value.
+ """
+ mult = 1.0 + 1.5 * tolerance # The 1.5 is a empirical hack; no math
return _calcCubicArcLengthCRecurse(mult, pt1, pt2, pt3, pt4)
@@ -66,12 +104,21 @@ def _dot(v1, v2):
def _intSecAtan(x):
# In : sympy.integrate(sp.sec(sp.atan(x)))
# Out: x*sqrt(x**2 + 1)/2 + asinh(x)/2
- return x * math.sqrt(x**2 + 1)/2 + math.asinh(x)/2
+ return x * math.sqrt(x ** 2 + 1) / 2 + math.asinh(x) / 2
def calcQuadraticArcLength(pt1, pt2, pt3):
- """Return the arc length for a qudratic bezier segment.
- pt1 and pt3 are the "anchor" points, pt2 is the "handle".
+ """Calculates the arc length for a quadratic Bezier segment.
+
+ Args:
+ pt1: Start point of the Bezier as 2D tuple.
+ pt2: Handle point of the Bezier as 2D tuple.
+ pt3: End point of the Bezier as 2D tuple.
+
+ Returns:
+ Arc length value.
+
+ Example::
>>> calcQuadraticArcLength((0, 0), (0, 0), (0, 0)) # empty segment
0.0
@@ -96,9 +143,16 @@ def calcQuadraticArcLength(pt1, pt2, pt3):
def calcQuadraticArcLengthC(pt1, pt2, pt3):
- """Return the arc length for a qudratic bezier segment using complex points.
- pt1 and pt3 are the "anchor" points, pt2 is the "handle"."""
+ """Calculates the arc length for a quadratic Bezier segment.
+
+ Args:
+ pt1: Start point of the Bezier as a complex number.
+ pt2: Handle point of the Bezier as a complex number.
+ pt3: End point of the Bezier as a complex number.
+ Returns:
+ Arc length value.
+ """
# Analytical solution to the length of a quadratic bezier.
# I'll explain how I arrived at this later.
d0 = pt2 - pt1
@@ -106,48 +160,82 @@ def calcQuadraticArcLengthC(pt1, pt2, pt3):
d = d1 - d0
n = d * 1j
scale = abs(n)
- if scale == 0.:
- return abs(pt3-pt1)
- origDist = _dot(n,d0)
+ if scale == 0.0:
+ return abs(pt3 - pt1)
+ origDist = _dot(n, d0)
if abs(origDist) < epsilon:
- if _dot(d0,d1) >= 0:
- return abs(pt3-pt1)
+ if _dot(d0, d1) >= 0:
+ return abs(pt3 - pt1)
a, b = abs(d0), abs(d1)
- return (a*a + b*b) / (a+b)
- x0 = _dot(d,d0) / origDist
- x1 = _dot(d,d1) / origDist
+ return (a * a + b * b) / (a + b)
+ x0 = _dot(d, d0) / origDist
+ x1 = _dot(d, d1) / origDist
Len = abs(2 * (_intSecAtan(x1) - _intSecAtan(x0)) * origDist / (scale * (x1 - x0)))
return Len
def approximateQuadraticArcLength(pt1, pt2, pt3):
- # Approximate length of quadratic Bezier curve using Gauss-Legendre quadrature
- # with n=3 points.
+ """Calculates the arc length for a quadratic Bezier segment.
+
+ Uses Gauss-Legendre quadrature for a branch-free approximation.
+ See :func:`calcQuadraticArcLength` for a slower but more accurate result.
+
+ Args:
+ pt1: Start point of the Bezier as 2D tuple.
+ pt2: Handle point of the Bezier as 2D tuple.
+ pt3: End point of the Bezier as 2D tuple.
+
+ Returns:
+ Approximate arc length value.
+ """
return approximateQuadraticArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3))
def approximateQuadraticArcLengthC(pt1, pt2, pt3):
- # Approximate length of quadratic Bezier curve using Gauss-Legendre quadrature
- # with n=3 points for complex points.
- #
+ """Calculates the arc length for a quadratic Bezier segment.
+
+ Uses Gauss-Legendre quadrature for a branch-free approximation.
+ See :func:`calcQuadraticArcLength` for a slower but more accurate result.
+
+ Args:
+ pt1: Start point of the Bezier as a complex number.
+ pt2: Handle point of the Bezier as a complex number.
+ pt3: End point of the Bezier as a complex number.
+
+ Returns:
+ Approximate arc length value.
+ """
# This, essentially, approximates the length-of-derivative function
# to be integrated with the best-matching fifth-degree polynomial
# approximation of it.
#
- #https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Legendre_quadrature
+ # https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Legendre_quadrature
# abs(BezierCurveC[2].diff(t).subs({t:T})) for T in sorted(.5, .5±sqrt(3/5)/2),
# weighted 5/18, 8/18, 5/18 respectively.
- v0 = abs(-0.492943519233745*pt1 + 0.430331482911935*pt2 + 0.0626120363218102*pt3)
- v1 = abs(pt3-pt1)*0.4444444444444444
- v2 = abs(-0.0626120363218102*pt1 - 0.430331482911935*pt2 + 0.492943519233745*pt3)
+ v0 = abs(
+ -0.492943519233745 * pt1 + 0.430331482911935 * pt2 + 0.0626120363218102 * pt3
+ )
+ v1 = abs(pt3 - pt1) * 0.4444444444444444
+ v2 = abs(
+ -0.0626120363218102 * pt1 - 0.430331482911935 * pt2 + 0.492943519233745 * pt3
+ )
return v0 + v1 + v2
def calcQuadraticBounds(pt1, pt2, pt3):
- """Return the bounding rectangle for a qudratic bezier segment.
- pt1 and pt3 are the "anchor" points, pt2 is the "handle".
+ """Calculates the bounding rectangle for a quadratic Bezier segment.
+
+ Args:
+ pt1: Start point of the Bezier as a 2D tuple.
+ pt2: Handle point of the Bezier as a 2D tuple.
+ pt3: End point of the Bezier as a 2D tuple.
+
+ Returns:
+ A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
+
+ Example::
>>> calcQuadraticBounds((0, 0), (50, 100), (100, 0))
(0, 0, 100, 50.0)
@@ -155,20 +243,34 @@ def calcQuadraticBounds(pt1, pt2, pt3):
(0.0, 0.0, 100, 100)
"""
(ax, ay), (bx, by), (cx, cy) = calcQuadraticParameters(pt1, pt2, pt3)
- ax2 = ax*2.0
- ay2 = ay*2.0
+ ax2 = ax * 2.0
+ ay2 = ay * 2.0
roots = []
if ax2 != 0:
- roots.append(-bx/ax2)
+ roots.append(-bx / ax2)
if ay2 != 0:
- roots.append(-by/ay2)
- points = [(ax*t*t + bx*t + cx, ay*t*t + by*t + cy) for t in roots if 0 <= t < 1] + [pt1, pt3]
+ roots.append(-by / ay2)
+ points = [
+ (ax * t * t + bx * t + cx, ay * t * t + by * t + cy)
+ for t in roots
+ if 0 <= t < 1
+ ] + [pt1, pt3]
return calcBounds(points)
def approximateCubicArcLength(pt1, pt2, pt3, pt4):
- """Return the approximate arc length for a cubic bezier segment.
- pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles".
+ """Approximates the arc length for a cubic Bezier segment.
+
+ Uses Gauss-Lobatto quadrature with n=5 points to approximate arc length.
+ See :func:`calcCubicArcLength` for a slower but more accurate result.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
+
+ Returns:
+ Arc length value.
+
+ Example::
>>> approximateCubicArcLength((0, 0), (25, 100), (75, 100), (100, 0))
190.04332968932817
@@ -181,18 +283,20 @@ def approximateCubicArcLength(pt1, pt2, pt3, pt4):
>>> approximateCubicArcLength((0, 0), (50, 0), (100, -50), (-50, 0)) # cusp
154.80848416537057
"""
- # Approximate length of cubic Bezier curve using Gauss-Lobatto quadrature
- # with n=5 points.
- return approximateCubicArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4))
+ return approximateCubicArcLengthC(
+ complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4)
+ )
def approximateCubicArcLengthC(pt1, pt2, pt3, pt4):
- """Return the approximate arc length for a cubic bezier segment of complex points.
- pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles"."""
+ """Approximates the arc length for a cubic Bezier segment.
- # Approximate length of cubic Bezier curve using Gauss-Lobatto quadrature
- # with n=5 points for complex points.
- #
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.
+
+ Returns:
+ Arc length value.
+ """
# This, essentially, approximates the length-of-derivative function
# to be integrated with the best-matching seventh-degree polynomial
# approximation of it.
@@ -201,18 +305,35 @@ def approximateCubicArcLengthC(pt1, pt2, pt3, pt4):
# abs(BezierCurveC[3].diff(t).subs({t:T})) for T in sorted(0, .5±(3/7)**.5/2, .5, 1),
# weighted 1/20, 49/180, 32/90, 49/180, 1/20 respectively.
- v0 = abs(pt2-pt1)*.15
- v1 = abs(-0.558983582205757*pt1 + 0.325650248872424*pt2 + 0.208983582205757*pt3 + 0.024349751127576*pt4)
- v2 = abs(pt4-pt1+pt3-pt2)*0.26666666666666666
- v3 = abs(-0.024349751127576*pt1 - 0.208983582205757*pt2 - 0.325650248872424*pt3 + 0.558983582205757*pt4)
- v4 = abs(pt4-pt3)*.15
+ v0 = abs(pt2 - pt1) * 0.15
+ v1 = abs(
+ -0.558983582205757 * pt1
+ + 0.325650248872424 * pt2
+ + 0.208983582205757 * pt3
+ + 0.024349751127576 * pt4
+ )
+ v2 = abs(pt4 - pt1 + pt3 - pt2) * 0.26666666666666666
+ v3 = abs(
+ -0.024349751127576 * pt1
+ - 0.208983582205757 * pt2
+ - 0.325650248872424 * pt3
+ + 0.558983582205757 * pt4
+ )
+ v4 = abs(pt4 - pt3) * 0.15
return v0 + v1 + v2 + v3 + v4
def calcCubicBounds(pt1, pt2, pt3, pt4):
- """Return the bounding rectangle for a cubic bezier segment.
- pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles".
+ """Calculates the bounding rectangle for a quadratic Bezier segment.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
+
+ Returns:
+ A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
+
+ Example::
>>> calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0))
(0, 0, 100, 75.0)
@@ -231,16 +352,33 @@ def calcCubicBounds(pt1, pt2, pt3, pt4):
yRoots = [t for t in solveQuadratic(ay3, by2, cy) if 0 <= t < 1]
roots = xRoots + yRoots
- points = [(ax*t*t*t + bx*t*t + cx * t + dx, ay*t*t*t + by*t*t + cy * t + dy) for t in roots] + [pt1, pt4]
+ points = [
+ (
+ ax * t * t * t + bx * t * t + cx * t + dx,
+ ay * t * t * t + by * t * t + cy * t + dy,
+ )
+ for t in roots
+ ] + [pt1, pt4]
return calcBounds(points)
def splitLine(pt1, pt2, where, isHorizontal):
- """Split the line between pt1 and pt2 at position 'where', which
- is an x coordinate if isHorizontal is False, a y coordinate if
- isHorizontal is True. Return a list of two line segments if the
- line was successfully split, or a list containing the original
- line.
+ """Split a line at a given coordinate.
+
+ Args:
+ pt1: Start point of line as 2D tuple.
+ pt2: End point of line as 2D tuple.
+ where: Position at which to split the line.
+ isHorizontal: Direction of the ray splitting the line. If true,
+ ``where`` is interpreted as a Y coordinate; if false, then
+ ``where`` is interpreted as an X coordinate.
+
+ Returns:
+ A list of two line segments (each line segment being two 2D tuples)
+ if the line was successfully split, or a list containing the original
+ line.
+
+ Example::
>>> printSegments(splitLine((0, 0), (100, 100), 50, True))
((0, 0), (50, 50))
@@ -263,8 +401,8 @@ def splitLine(pt1, pt2, where, isHorizontal):
pt1x, pt1y = pt1
pt2x, pt2y = pt2
- ax = (pt2x - pt1x)
- ay = (pt2y - pt1y)
+ ax = pt2x - pt1x
+ ay = pt2y - pt1y
bx = pt1x
by = pt1y
@@ -282,9 +420,21 @@ def splitLine(pt1, pt2, where, isHorizontal):
def splitQuadratic(pt1, pt2, pt3, where, isHorizontal):
- """Split the quadratic curve between pt1, pt2 and pt3 at position 'where',
- which is an x coordinate if isHorizontal is False, a y coordinate if
- isHorizontal is True. Return a list of curve segments.
+ """Split a quadratic Bezier curve at a given coordinate.
+
+ Args:
+ pt1,pt2,pt3: Control points of the Bezier as 2D tuples.
+ where: Position at which to split the curve.
+ isHorizontal: Direction of the ray splitting the curve. If true,
+ ``where`` is interpreted as a Y coordinate; if false, then
+ ``where`` is interpreted as an X coordinate.
+
+ Returns:
+ A list of two curve segments (each curve segment being three 2D tuples)
+ if the curve was successfully split, or a list containing the original
+ curve.
+
+ Example::
>>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 150, False))
((0, 0), (50, 100), (100, 0))
@@ -305,18 +455,31 @@ def splitQuadratic(pt1, pt2, pt3, where, isHorizontal):
((50, 50), (75, 50), (100, 0))
"""
a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
- solutions = solveQuadratic(a[isHorizontal], b[isHorizontal],
- c[isHorizontal] - where)
- solutions = sorted([t for t in solutions if 0 <= t < 1])
+ solutions = solveQuadratic(
+ a[isHorizontal], b[isHorizontal], c[isHorizontal] - where
+ )
+ solutions = sorted(t for t in solutions if 0 <= t < 1)
if not solutions:
return [(pt1, pt2, pt3)]
return _splitQuadraticAtT(a, b, c, *solutions)
def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal):
- """Split the cubic curve between pt1, pt2, pt3 and pt4 at position 'where',
- which is an x coordinate if isHorizontal is False, a y coordinate if
- isHorizontal is True. Return a list of curve segments.
+ """Split a cubic Bezier curve at a given coordinate.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
+ where: Position at which to split the curve.
+ isHorizontal: Direction of the ray splitting the curve. If true,
+ ``where`` is interpreted as a Y coordinate; if false, then
+ ``where`` is interpreted as an X coordinate.
+
+ Returns:
+ A list of two curve segments (each curve segment being four 2D tuples)
+ if the curve was successfully split, or a list containing the original
+ curve.
+
+ Example::
>>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 150, False))
((0, 0), (25, 100), (75, 100), (100, 0))
@@ -329,17 +492,26 @@ def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal):
((92.5259, 25), (95.202, 17.5085), (97.7062, 9.17517), (100, 1.77636e-15))
"""
a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
- solutions = solveCubic(a[isHorizontal], b[isHorizontal], c[isHorizontal],
- d[isHorizontal] - where)
- solutions = sorted([t for t in solutions if 0 <= t < 1])
+ solutions = solveCubic(
+ a[isHorizontal], b[isHorizontal], c[isHorizontal], d[isHorizontal] - where
+ )
+ solutions = sorted(t for t in solutions if 0 <= t < 1)
if not solutions:
return [(pt1, pt2, pt3, pt4)]
return _splitCubicAtT(a, b, c, d, *solutions)
def splitQuadraticAtT(pt1, pt2, pt3, *ts):
- """Split the quadratic curve between pt1, pt2 and pt3 at one or more
- values of t. Return a list of curve segments.
+ """Split a quadratic Bezier curve at one or more values of t.
+
+ Args:
+ pt1,pt2,pt3: Control points of the Bezier as 2D tuples.
+ *ts: Positions at which to split the curve.
+
+ Returns:
+ A list of curve segments (each curve segment being three 2D tuples).
+
+ Examples::
>>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5))
((0, 0), (25, 50), (50, 50))
@@ -354,8 +526,16 @@ def splitQuadraticAtT(pt1, pt2, pt3, *ts):
def splitCubicAtT(pt1, pt2, pt3, pt4, *ts):
- """Split the cubic curve between pt1, pt2, pt3 and pt4 at one or more
- values of t. Return a list of curve segments.
+ """Split a cubic Bezier curve at one or more values of t.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
+ *ts: Positions at which to split the curve.
+
+ Returns:
+ A list of curve segments (each curve segment being four 2D tuples).
+
+ Examples::
>>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5))
((0, 0), (12.5, 50), (31.25, 75), (50, 75))
@@ -379,17 +559,17 @@ def _splitQuadraticAtT(a, b, c, *ts):
cx, cy = c
for i in range(len(ts) - 1):
t1 = ts[i]
- t2 = ts[i+1]
- delta = (t2 - t1)
+ t2 = ts[i + 1]
+ delta = t2 - t1
# calc new a, b and c
- delta_2 = delta*delta
+ delta_2 = delta * delta
a1x = ax * delta_2
a1y = ay * delta_2
- b1x = (2*ax*t1 + bx) * delta
- b1y = (2*ay*t1 + by) * delta
- t1_2 = t1*t1
- c1x = ax*t1_2 + bx*t1 + cx
- c1y = ay*t1_2 + by*t1 + cy
+ b1x = (2 * ax * t1 + bx) * delta
+ b1y = (2 * ay * t1 + by) * delta
+ t1_2 = t1 * t1
+ c1x = ax * t1_2 + bx * t1 + cx
+ c1y = ay * t1_2 + by * t1 + cy
pt1, pt2, pt3 = calcQuadraticPoints((a1x, a1y), (b1x, b1y), (c1x, c1y))
segments.append((pt1, pt2, pt3))
@@ -407,24 +587,26 @@ def _splitCubicAtT(a, b, c, d, *ts):
dx, dy = d
for i in range(len(ts) - 1):
t1 = ts[i]
- t2 = ts[i+1]
- delta = (t2 - t1)
+ t2 = ts[i + 1]
+ delta = t2 - t1
- delta_2 = delta*delta
- delta_3 = delta*delta_2
- t1_2 = t1*t1
- t1_3 = t1*t1_2
+ delta_2 = delta * delta
+ delta_3 = delta * delta_2
+ t1_2 = t1 * t1
+ t1_3 = t1 * t1_2
# calc new a, b, c and d
a1x = ax * delta_3
a1y = ay * delta_3
- b1x = (3*ax*t1 + bx) * delta_2
- b1y = (3*ay*t1 + by) * delta_2
- c1x = (2*bx*t1 + cx + 3*ax*t1_2) * delta
- c1y = (2*by*t1 + cy + 3*ay*t1_2) * delta
- d1x = ax*t1_3 + bx*t1_2 + cx*t1 + dx
- d1y = ay*t1_3 + by*t1_2 + cy*t1 + dy
- pt1, pt2, pt3, pt4 = calcCubicPoints((a1x, a1y), (b1x, b1y), (c1x, c1y), (d1x, d1y))
+ b1x = (3 * ax * t1 + bx) * delta_2
+ b1y = (3 * ay * t1 + by) * delta_2
+ c1x = (2 * bx * t1 + cx + 3 * ax * t1_2) * delta
+ c1y = (2 * by * t1 + cy + 3 * ay * t1_2) * delta
+ d1x = ax * t1_3 + bx * t1_2 + cx * t1 + dx
+ d1y = ay * t1_3 + by * t1_2 + cy * t1 + dy
+ pt1, pt2, pt3, pt4 = calcCubicPoints(
+ (a1x, a1y), (b1x, b1y), (c1x, c1y), (d1x, d1y)
+ )
segments.append((pt1, pt2, pt3, pt4))
return segments
@@ -436,12 +618,19 @@ def _splitCubicAtT(a, b, c, d, *ts):
from math import sqrt, acos, cos, pi
-def solveQuadratic(a, b, c,
- sqrt=sqrt):
- """Solve a quadratic equation where a, b and c are real.
- a*x*x + b*x + c = 0
- This function returns a list of roots. Note that the returned list
- is neither guaranteed to be sorted nor to contain unique values!
+def solveQuadratic(a, b, c, sqrt=sqrt):
+ """Solve a quadratic equation.
+
+ Solves *a*x*x + b*x + c = 0* where a, b and c are real.
+
+ Args:
+ a: coefficient of *x²*
+ b: coefficient of *x*
+ c: constant term
+
+ Returns:
+ A list of roots. Note that the returned list is neither guaranteed to
+ be sorted nor to contain unique values!
"""
if abs(a) < epsilon:
if abs(b) < epsilon:
@@ -449,13 +638,13 @@ def solveQuadratic(a, b, c,
roots = []
else:
# We have a linear equation with 1 root.
- roots = [-c/b]
+ roots = [-c / b]
else:
# We have a true quadratic equation. Apply the quadratic formula to find two roots.
- DD = b*b - 4.0*a*c
+ DD = b * b - 4.0 * a * c
if DD >= 0.0:
rDD = sqrt(DD)
- roots = [(-b+rDD)/2.0/a, (-b-rDD)/2.0/a]
+ roots = [(-b + rDD) / 2.0 / a, (-b - rDD) / 2.0 / a]
else:
# complex roots, ignore
roots = []
@@ -463,25 +652,36 @@ def solveQuadratic(a, b, c,
def solveCubic(a, b, c, d):
- """Solve a cubic equation where a, b, c and d are real.
- a*x*x*x + b*x*x + c*x + d = 0
- This function returns a list of roots. Note that the returned list
- is neither guaranteed to be sorted nor to contain unique values!
-
- >>> solveCubic(1, 1, -6, 0)
- [-3.0, -0.0, 2.0]
- >>> solveCubic(-10.0, -9.0, 48.0, -29.0)
- [-2.9, 1.0, 1.0]
- >>> solveCubic(-9.875, -9.0, 47.625, -28.75)
- [-2.911392, 1.0, 1.0]
- >>> solveCubic(1.0, -4.5, 6.75, -3.375)
- [1.5, 1.5, 1.5]
- >>> solveCubic(-12.0, 18.0, -9.0, 1.50023651123)
- [0.5, 0.5, 0.5]
- >>> solveCubic(
- ... 9.0, 0.0, 0.0, -7.62939453125e-05
- ... ) == [-0.0, -0.0, -0.0]
- True
+ """Solve a cubic equation.
+
+ Solves *a*x*x*x + b*x*x + c*x + d = 0* where a, b, c and d are real.
+
+ Args:
+ a: coefficient of *x³*
+ b: coefficient of *x²*
+ c: coefficient of *x*
+ d: constant term
+
+ Returns:
+ A list of roots. Note that the returned list is neither guaranteed to
+ be sorted nor to contain unique values!
+
+ Examples::
+
+ >>> solveCubic(1, 1, -6, 0)
+ [-3.0, -0.0, 2.0]
+ >>> solveCubic(-10.0, -9.0, 48.0, -29.0)
+ [-2.9, 1.0, 1.0]
+ >>> solveCubic(-9.875, -9.0, 47.625, -28.75)
+ [-2.911392, 1.0, 1.0]
+ >>> solveCubic(1.0, -4.5, 6.75, -3.375)
+ [1.5, 1.5, 1.5]
+ >>> solveCubic(-12.0, 18.0, -9.0, 1.50023651123)
+ [0.5, 0.5, 0.5]
+ >>> solveCubic(
+ ... 9.0, 0.0, 0.0, -7.62939453125e-05
+ ... ) == [-0.0, -0.0, -0.0]
+ True
"""
#
# adapted from:
@@ -494,52 +694,52 @@ def solveCubic(a, b, c, d):
# returns unreliable results, so we fall back to quad.
return solveQuadratic(b, c, d)
a = float(a)
- a1 = b/a
- a2 = c/a
- a3 = d/a
+ a1 = b / a
+ a2 = c / a
+ a3 = d / a
- Q = (a1*a1 - 3.0*a2)/9.0
- R = (2.0*a1*a1*a1 - 9.0*a1*a2 + 27.0*a3)/54.0
+ Q = (a1 * a1 - 3.0 * a2) / 9.0
+ R = (2.0 * a1 * a1 * a1 - 9.0 * a1 * a2 + 27.0 * a3) / 54.0
- R2 = R*R
- Q3 = Q*Q*Q
+ R2 = R * R
+ Q3 = Q * Q * Q
R2 = 0 if R2 < epsilon else R2
Q3 = 0 if abs(Q3) < epsilon else Q3
R2_Q3 = R2 - Q3
- if R2 == 0. and Q3 == 0.:
- x = round(-a1/3.0, epsilonDigits)
+ if R2 == 0.0 and Q3 == 0.0:
+ x = round(-a1 / 3.0, epsilonDigits)
return [x, x, x]
- elif R2_Q3 <= epsilon * .5:
+ elif R2_Q3 <= epsilon * 0.5:
# The epsilon * .5 above ensures that Q3 is not zero.
- theta = acos(max(min(R/sqrt(Q3), 1.0), -1.0))
- rQ2 = -2.0*sqrt(Q)
- a1_3 = a1/3.0
- x0 = rQ2*cos(theta/3.0) - a1_3
- x1 = rQ2*cos((theta+2.0*pi)/3.0) - a1_3
- x2 = rQ2*cos((theta+4.0*pi)/3.0) - a1_3
+ theta = acos(max(min(R / sqrt(Q3), 1.0), -1.0))
+ rQ2 = -2.0 * sqrt(Q)
+ a1_3 = a1 / 3.0
+ x0 = rQ2 * cos(theta / 3.0) - a1_3
+ x1 = rQ2 * cos((theta + 2.0 * pi) / 3.0) - a1_3
+ x2 = rQ2 * cos((theta + 4.0 * pi) / 3.0) - a1_3
x0, x1, x2 = sorted([x0, x1, x2])
# Merge roots that are close-enough
if x1 - x0 < epsilon and x2 - x1 < epsilon:
- x0 = x1 = x2 = round((x0 + x1 + x2) / 3., epsilonDigits)
+ x0 = x1 = x2 = round((x0 + x1 + x2) / 3.0, epsilonDigits)
elif x1 - x0 < epsilon:
- x0 = x1 = round((x0 + x1) / 2., epsilonDigits)
+ x0 = x1 = round((x0 + x1) / 2.0, epsilonDigits)
x2 = round(x2, epsilonDigits)
elif x2 - x1 < epsilon:
x0 = round(x0, epsilonDigits)
- x1 = x2 = round((x1 + x2) / 2., epsilonDigits)
+ x1 = x2 = round((x1 + x2) / 2.0, epsilonDigits)
else:
x0 = round(x0, epsilonDigits)
x1 = round(x1, epsilonDigits)
x2 = round(x2, epsilonDigits)
return [x0, x1, x2]
else:
- x = pow(sqrt(R2_Q3)+abs(R), 1/3.0)
- x = x + Q/x
+ x = pow(sqrt(R2_Q3) + abs(R), 1 / 3.0)
+ x = x + Q / x
if R >= 0.0:
x = -x
- x = round(x - a1/3.0, epsilonDigits)
+ x = round(x - a1 / 3.0, epsilonDigits)
return [x]
@@ -547,6 +747,7 @@ def solveCubic(a, b, c, d):
# Conversion routines for points to parameters and vice versa
#
+
def calcQuadraticParameters(pt1, pt2, pt3):
x2, y2 = pt2
x3, y3 = pt3
@@ -563,8 +764,8 @@ def calcCubicParameters(pt1, pt2, pt3, pt4):
x3, y3 = pt3
x4, y4 = pt4
dx, dy = pt1
- cx = (x2 -dx) * 3.0
- cy = (y2 -dy) * 3.0
+ cx = (x2 - dx) * 3.0
+ cy = (y2 - dy) * 3.0
bx = (x3 - x2) * 3.0 - cx
by = (y3 - y2) * 3.0 - cy
ax = x4 - dx - cx - bx
@@ -601,17 +802,406 @@ def calcCubicPoints(a, b, c, d):
return (x1, y1), (x2, y2), (x3, y3), (x4, y4)
+#
+# Point at time
+#
+
+
+def linePointAtT(pt1, pt2, t):
+ """Finds the point at time `t` on a line.
+
+ Args:
+ pt1, pt2: Coordinates of the line as 2D tuples.
+ t: The time along the line.
+
+ Returns:
+ A 2D tuple with the coordinates of the point.
+ """
+ return ((pt1[0] * (1 - t) + pt2[0] * t), (pt1[1] * (1 - t) + pt2[1] * t))
+
+
+def quadraticPointAtT(pt1, pt2, pt3, t):
+ """Finds the point at time `t` on a quadratic curve.
+
+ Args:
+ pt1, pt2, pt3: Coordinates of the curve as 2D tuples.
+ t: The time along the curve.
+
+ Returns:
+ A 2D tuple with the coordinates of the point.
+ """
+ x = (1 - t) * (1 - t) * pt1[0] + 2 * (1 - t) * t * pt2[0] + t * t * pt3[0]
+ y = (1 - t) * (1 - t) * pt1[1] + 2 * (1 - t) * t * pt2[1] + t * t * pt3[1]
+ return (x, y)
+
+
+def cubicPointAtT(pt1, pt2, pt3, pt4, t):
+ """Finds the point at time `t` on a cubic curve.
+
+ Args:
+ pt1, pt2, pt3, pt4: Coordinates of the curve as 2D tuples.
+ t: The time along the curve.
+
+ Returns:
+ A 2D tuple with the coordinates of the point.
+ """
+ x = (
+ (1 - t) * (1 - t) * (1 - t) * pt1[0]
+ + 3 * (1 - t) * (1 - t) * t * pt2[0]
+ + 3 * (1 - t) * t * t * pt3[0]
+ + t * t * t * pt4[0]
+ )
+ y = (
+ (1 - t) * (1 - t) * (1 - t) * pt1[1]
+ + 3 * (1 - t) * (1 - t) * t * pt2[1]
+ + 3 * (1 - t) * t * t * pt3[1]
+ + t * t * t * pt4[1]
+ )
+ return (x, y)
+
+
+def segmentPointAtT(seg, t):
+ if len(seg) == 2:
+ return linePointAtT(*seg, t)
+ elif len(seg) == 3:
+ return quadraticPointAtT(*seg, t)
+ elif len(seg) == 4:
+ return cubicPointAtT(*seg, t)
+ raise ValueError("Unknown curve degree")
+
+
+#
+# Intersection finders
+#
+
+
+def _line_t_of_pt(s, e, pt):
+ sx, sy = s
+ ex, ey = e
+ px, py = pt
+ if not math.isclose(sx, ex):
+ return (px - sx) / (ex - sx)
+ if not math.isclose(sy, ey):
+ return (py - sy) / (ey - sy)
+ # Line is a point!
+ return -1
+
+
+def _both_points_are_on_same_side_of_origin(a, b, origin):
+ xDiff = (a[0] - origin[0]) * (b[0] - origin[0])
+ yDiff = (a[1] - origin[1]) * (b[1] - origin[1])
+ return not (xDiff <= 0.0 and yDiff <= 0.0)
+
+
+def lineLineIntersections(s1, e1, s2, e2):
+ """Finds intersections between two line segments.
+
+ Args:
+ s1, e1: Coordinates of the first line as 2D tuples.
+ s2, e2: Coordinates of the second line as 2D tuples.
+
+ Returns:
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
+ and ``t2`` attributes containing the intersection point, time on first
+ segment and time on second segment respectively.
+
+ Examples::
+
+ >>> a = lineLineIntersections( (310,389), (453, 222), (289, 251), (447, 367))
+ >>> len(a)
+ 1
+ >>> intersection = a[0]
+ >>> intersection.pt
+ (374.44882952482897, 313.73458370177315)
+ >>> (intersection.t1, intersection.t2)
+ (0.45069111555824454, 0.5408153767394238)
+ """
+ s1x, s1y = s1
+ e1x, e1y = e1
+ s2x, s2y = s2
+ e2x, e2y = e2
+ if (
+ math.isclose(s2x, e2x) and math.isclose(s1x, e1x) and not math.isclose(s1x, s2x)
+ ): # Parallel vertical
+ return []
+ if (
+ math.isclose(s2y, e2y) and math.isclose(s1y, e1y) and not math.isclose(s1y, s2y)
+ ): # Parallel horizontal
+ return []
+ if math.isclose(s2x, e2x) and math.isclose(s2y, e2y): # Line segment is tiny
+ return []
+ if math.isclose(s1x, e1x) and math.isclose(s1y, e1y): # Line segment is tiny
+ return []
+ if math.isclose(e1x, s1x):
+ x = s1x
+ slope34 = (e2y - s2y) / (e2x - s2x)
+ y = slope34 * (x - s2x) + s2y
+ pt = (x, y)
+ return [
+ Intersection(
+ pt=pt, t1=_line_t_of_pt(s1, e1, pt), t2=_line_t_of_pt(s2, e2, pt)
+ )
+ ]
+ if math.isclose(s2x, e2x):
+ x = s2x
+ slope12 = (e1y - s1y) / (e1x - s1x)
+ y = slope12 * (x - s1x) + s1y
+ pt = (x, y)
+ return [
+ Intersection(
+ pt=pt, t1=_line_t_of_pt(s1, e1, pt), t2=_line_t_of_pt(s2, e2, pt)
+ )
+ ]
+
+ slope12 = (e1y - s1y) / (e1x - s1x)
+ slope34 = (e2y - s2y) / (e2x - s2x)
+ if math.isclose(slope12, slope34):
+ return []
+ x = (slope12 * s1x - s1y - slope34 * s2x + s2y) / (slope12 - slope34)
+ y = slope12 * (x - s1x) + s1y
+ pt = (x, y)
+ if _both_points_are_on_same_side_of_origin(
+ pt, e1, s1
+ ) and _both_points_are_on_same_side_of_origin(pt, s2, e2):
+ return [
+ Intersection(
+ pt=pt, t1=_line_t_of_pt(s1, e1, pt), t2=_line_t_of_pt(s2, e2, pt)
+ )
+ ]
+ return []
+
+
+def _alignment_transformation(segment):
+ # Returns a transformation which aligns a segment horizontally at the
+ # origin. Apply this transformation to curves and root-find to find
+ # intersections with the segment.
+ start = segment[0]
+ end = segment[-1]
+ angle = math.atan2(end[1] - start[1], end[0] - start[0])
+ return Identity.rotate(-angle).translate(-start[0], -start[1])
+
+
+def _curve_line_intersections_t(curve, line):
+ aligned_curve = _alignment_transformation(line).transformPoints(curve)
+ if len(curve) == 3:
+ a, b, c = calcQuadraticParameters(*aligned_curve)
+ intersections = solveQuadratic(a[1], b[1], c[1])
+ elif len(curve) == 4:
+ a, b, c, d = calcCubicParameters(*aligned_curve)
+ intersections = solveCubic(a[1], b[1], c[1], d[1])
+ else:
+ raise ValueError("Unknown curve degree")
+ return sorted(i for i in intersections if 0.0 <= i <= 1)
+
+
+def curveLineIntersections(curve, line):
+ """Finds intersections between a curve and a line.
+
+ Args:
+ curve: List of coordinates of the curve segment as 2D tuples.
+ line: List of coordinates of the line segment as 2D tuples.
+
+ Returns:
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
+ and ``t2`` attributes containing the intersection point, time on first
+ segment and time on second segment respectively.
+
+ Examples::
+ >>> curve = [ (100, 240), (30, 60), (210, 230), (160, 30) ]
+ >>> line = [ (25, 260), (230, 20) ]
+ >>> intersections = curveLineIntersections(curve, line)
+ >>> len(intersections)
+ 3
+ >>> intersections[0].pt
+ (84.90010344084885, 189.87306176459828)
+ """
+ if len(curve) == 3:
+ pointFinder = quadraticPointAtT
+ elif len(curve) == 4:
+ pointFinder = cubicPointAtT
+ else:
+ raise ValueError("Unknown curve degree")
+ intersections = []
+ for t in _curve_line_intersections_t(curve, line):
+ pt = pointFinder(*curve, t)
+ intersections.append(Intersection(pt=pt, t1=t, t2=_line_t_of_pt(*line, pt)))
+ return intersections
+
+
+def _curve_bounds(c):
+ if len(c) == 3:
+ return calcQuadraticBounds(*c)
+ elif len(c) == 4:
+ return calcCubicBounds(*c)
+ raise ValueError("Unknown curve degree")
+
+
+def _split_segment_at_t(c, t):
+ if len(c) == 2:
+ s, e = c
+ midpoint = linePointAtT(s, e, t)
+ return [(s, midpoint), (midpoint, e)]
+ if len(c) == 3:
+ return splitQuadraticAtT(*c, t)
+ elif len(c) == 4:
+ return splitCubicAtT(*c, t)
+ raise ValueError("Unknown curve degree")
+
+
+def _curve_curve_intersections_t(
+ curve1, curve2, precision=1e-3, range1=None, range2=None
+):
+ bounds1 = _curve_bounds(curve1)
+ bounds2 = _curve_bounds(curve2)
+
+ if not range1:
+ range1 = (0.0, 1.0)
+ if not range2:
+ range2 = (0.0, 1.0)
+
+ # If bounds don't intersect, go home
+ intersects, _ = sectRect(bounds1, bounds2)
+ if not intersects:
+ return []
+
+ def midpoint(r):
+ return 0.5 * (r[0] + r[1])
+
+ # If they do overlap but they're tiny, approximate
+ if rectArea(bounds1) < precision and rectArea(bounds2) < precision:
+ return [(midpoint(range1), midpoint(range2))]
+
+ c11, c12 = _split_segment_at_t(curve1, 0.5)
+ c11_range = (range1[0], midpoint(range1))
+ c12_range = (midpoint(range1), range1[1])
+
+ c21, c22 = _split_segment_at_t(curve2, 0.5)
+ c21_range = (range2[0], midpoint(range2))
+ c22_range = (midpoint(range2), range2[1])
+
+ found = []
+ found.extend(
+ _curve_curve_intersections_t(
+ c11, c21, precision, range1=c11_range, range2=c21_range
+ )
+ )
+ found.extend(
+ _curve_curve_intersections_t(
+ c12, c21, precision, range1=c12_range, range2=c21_range
+ )
+ )
+ found.extend(
+ _curve_curve_intersections_t(
+ c11, c22, precision, range1=c11_range, range2=c22_range
+ )
+ )
+ found.extend(
+ _curve_curve_intersections_t(
+ c12, c22, precision, range1=c12_range, range2=c22_range
+ )
+ )
+
+ unique_key = lambda ts: (int(ts[0] / precision), int(ts[1] / precision))
+ seen = set()
+ unique_values = []
+
+ for ts in found:
+ key = unique_key(ts)
+ if key in seen:
+ continue
+ seen.add(key)
+ unique_values.append(ts)
+
+ return unique_values
+
+
+def curveCurveIntersections(curve1, curve2):
+ """Finds intersections between a curve and a curve.
+
+ Args:
+ curve1: List of coordinates of the first curve segment as 2D tuples.
+ curve2: List of coordinates of the second curve segment as 2D tuples.
+
+ Returns:
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
+ and ``t2`` attributes containing the intersection point, time on first
+ segment and time on second segment respectively.
+
+ Examples::
+ >>> curve1 = [ (10,100), (90,30), (40,140), (220,220) ]
+ >>> curve2 = [ (5,150), (180,20), (80,250), (210,190) ]
+ >>> intersections = curveCurveIntersections(curve1, curve2)
+ >>> len(intersections)
+ 3
+ >>> intersections[0].pt
+ (81.7831487395506, 109.88904552375288)
+ """
+ intersection_ts = _curve_curve_intersections_t(curve1, curve2)
+ return [
+ Intersection(pt=segmentPointAtT(curve1, ts[0]), t1=ts[0], t2=ts[1])
+ for ts in intersection_ts
+ ]
+
+
+def segmentSegmentIntersections(seg1, seg2):
+ """Finds intersections between two segments.
+
+ Args:
+ seg1: List of coordinates of the first segment as 2D tuples.
+ seg2: List of coordinates of the second segment as 2D tuples.
+
+ Returns:
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
+ and ``t2`` attributes containing the intersection point, time on first
+ segment and time on second segment respectively.
+
+ Examples::
+ >>> curve1 = [ (10,100), (90,30), (40,140), (220,220) ]
+ >>> curve2 = [ (5,150), (180,20), (80,250), (210,190) ]
+ >>> intersections = segmentSegmentIntersections(curve1, curve2)
+ >>> len(intersections)
+ 3
+ >>> intersections[0].pt
+ (81.7831487395506, 109.88904552375288)
+ >>> curve3 = [ (100, 240), (30, 60), (210, 230), (160, 30) ]
+ >>> line = [ (25, 260), (230, 20) ]
+ >>> intersections = segmentSegmentIntersections(curve3, line)
+ >>> len(intersections)
+ 3
+ >>> intersections[0].pt
+ (84.90010344084885, 189.87306176459828)
+
+ """
+ # Arrange by degree
+ swapped = False
+ if len(seg2) > len(seg1):
+ seg2, seg1 = seg1, seg2
+ swapped = True
+ if len(seg1) > 2:
+ if len(seg2) > 2:
+ intersections = curveCurveIntersections(seg1, seg2)
+ else:
+ intersections = curveLineIntersections(seg1, seg2)
+ elif len(seg1) == 2 and len(seg2) == 2:
+ intersections = lineLineIntersections(*seg1, *seg2)
+ else:
+ raise ValueError("Couldn't work out which intersection function to use")
+ if not swapped:
+ return intersections
+ return [Intersection(pt=i.pt, t1=i.t2, t2=i.t1) for i in intersections]
+
+
def _segmentrepr(obj):
"""
- >>> _segmentrepr([1, [2, 3], [], [[2, [3, 4], [0.1, 2.2]]]])
- '(1, (2, 3), (), ((2, (3, 4), (0.1, 2.2))))'
+ >>> _segmentrepr([1, [2, 3], [], [[2, [3, 4], [0.1, 2.2]]]])
+ '(1, (2, 3), (), ((2, (3, 4), (0.1, 2.2))))'
"""
try:
it = iter(obj)
except TypeError:
return "%g" % obj
else:
- return "(%s)" % ", ".join([_segmentrepr(x) for x in it])
+ return "(%s)" % ", ".join(_segmentrepr(x) for x in it)
def printSegments(segments):
@@ -621,7 +1211,9 @@ def printSegments(segments):
for segment in segments:
print(_segmentrepr(segment))
+
if __name__ == "__main__":
import sys
import doctest
+
sys.exit(doctest.testmod().failed)
diff --git a/Lib/fontTools/misc/classifyTools.py b/Lib/fontTools/misc/classifyTools.py
index 64bcdc8a..ae88a8f7 100644
--- a/Lib/fontTools/misc/classifyTools.py
+++ b/Lib/fontTools/misc/classifyTools.py
@@ -1,8 +1,6 @@
""" fontTools.misc.classifyTools.py -- tools for classifying things.
"""
-from __future__ import print_function, absolute_import
-from fontTools.misc.py23 import *
class Classifier(object):
diff --git a/Lib/fontTools/misc/cliTools.py b/Lib/fontTools/misc/cliTools.py
index 8420e3e7..e8c17677 100644
--- a/Lib/fontTools/misc/cliTools.py
+++ b/Lib/fontTools/misc/cliTools.py
@@ -1,6 +1,4 @@
"""Collection of utilities for command-line interfaces and console scripts."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import os
import re
@@ -9,6 +7,28 @@ numberAddedRE = re.compile(r"#\d+$")
def makeOutputFileName(input, outputDir=None, extension=None, overWrite=False):
+ """Generates a suitable file name for writing output.
+
+ Often tools will want to take a file, do some kind of transformation to it,
+ and write it out again. This function determines an appropriate name for the
+ output file, through one or more of the following steps:
+
+ - changing the output directory
+ - replacing the file extension
+ - suffixing the filename with a number (``#1``, ``#2``, etc.) to avoid
+ overwriting an existing file.
+
+ Args:
+ input: Name of input file.
+ outputDir: Optionally, a new directory to write the file into.
+ extension: Optionally, a replacement for the current file extension.
+ overWrite: Overwriting an existing file is permitted if true; if false
+ and the proposed filename exists, a new name will be generated by
+ adding an appropriate number suffix.
+
+ Returns:
+ str: Suitable output filename
+ """
dirName, fileName = os.path.split(input)
fileName, ext = os.path.splitext(fileName)
if outputDir:
diff --git a/Lib/fontTools/misc/cython.py b/Lib/fontTools/misc/cython.py
new file mode 100644
index 00000000..0ba659f6
--- /dev/null
+++ b/Lib/fontTools/misc/cython.py
@@ -0,0 +1,25 @@
+""" Exports a no-op 'cython' namespace similar to
+https://github.com/cython/cython/blob/master/Cython/Shadow.py
+
+This allows to optionally compile @cython decorated functions
+(when cython is available at built time), or run the same code
+as pure-python, without runtime dependency on cython module.
+
+We only define the symbols that we use. E.g. see fontTools.cu2qu
+"""
+
+from types import SimpleNamespace
+
+def _empty_decorator(x):
+ return x
+
+compiled = False
+
+for name in ("double", "complex", "int"):
+ globals()[name] = None
+
+for name in ("cfunc", "inline"):
+ globals()[name] = _empty_decorator
+
+locals = lambda **_: _empty_decorator
+returns = lambda _: _empty_decorator
diff --git a/Lib/fontTools/misc/dictTools.py b/Lib/fontTools/misc/dictTools.py
index db5d6587..ae7932c9 100644
--- a/Lib/fontTools/misc/dictTools.py
+++ b/Lib/fontTools/misc/dictTools.py
@@ -1,7 +1,5 @@
"""Misc dict tools."""
-from __future__ import print_function, absolute_import, division
-from fontTools.misc.py23 import *
__all__ = ['hashdict']
diff --git a/Lib/fontTools/misc/eexec.py b/Lib/fontTools/misc/eexec.py
index 0efa6f56..71f733c1 100644
--- a/Lib/fontTools/misc/eexec.py
+++ b/Lib/fontTools/misc/eexec.py
@@ -1,9 +1,19 @@
-"""fontTools.misc.eexec.py -- Module implementing the eexec and
-charstring encryption algorithm as used by PostScript Type 1 fonts.
"""
+PostScript Type 1 fonts make use of two types of encryption: charstring
+encryption and ``eexec`` encryption. Charstring encryption is used for
+the charstrings themselves, while ``eexec`` is used to encrypt larger
+sections of the font program, such as the ``Private`` and ``CharStrings``
+dictionaries. Despite the different names, the algorithm is the same,
+although ``eexec`` encryption uses a fixed initial key R=55665.
+
+The algorithm uses cipher feedback, meaning that the ciphertext is used
+to modify the key. Because of this, the routines in this module return
+the new key at the end of the operation.
+
+"""
+
+from fontTools.misc.py23 import bytechr, bytesjoin, byteord
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
def _decryptChar(cipher, R):
cipher = byteord(cipher)
@@ -20,12 +30,24 @@ def _encryptChar(plain, R):
def decrypt(cipherstring, R):
r"""
- >>> testStr = b"\0\0asdadads asds\265"
- >>> decryptedStr, R = decrypt(testStr, 12321)
- >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
- True
- >>> R == 36142
- True
+ Decrypts a string using the Type 1 encryption algorithm.
+
+ Args:
+ cipherstring: String of ciphertext.
+ R: Initial key.
+
+ Returns:
+ decryptedStr: Plaintext string.
+ R: Output key for subsequent decryptions.
+
+ Examples::
+
+ >>> testStr = b"\0\0asdadads asds\265"
+ >>> decryptedStr, R = decrypt(testStr, 12321)
+ >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
+ True
+ >>> R == 36142
+ True
"""
plainList = []
for cipher in cipherstring:
@@ -36,6 +58,30 @@ def decrypt(cipherstring, R):
def encrypt(plainstring, R):
r"""
+ Encrypts a string using the Type 1 encryption algorithm.
+
+ Note that the algorithm as described in the Type 1 specification requires the
+ plaintext to be prefixed with a number of random bytes. (For ``eexec`` the
+ number of random bytes is set to 4.) This routine does *not* add the random
+ prefix to its input.
+
+ Args:
+ plainstring: String of plaintext.
+ R: Initial key.
+
+ Returns:
+ cipherstring: Ciphertext string.
+ R: Output key for subsequent encryptions.
+
+ Examples::
+
+ >>> testStr = b"\0\0asdadads asds\265"
+ >>> decryptedStr, R = decrypt(testStr, 12321)
+ >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
+ True
+ >>> R == 36142
+ True
+
>>> testStr = b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
>>> encryptedStr, R = encrypt(testStr, 12321)
>>> encryptedStr == b"\0\0asdadads asds\265"
diff --git a/Lib/fontTools/misc/encodingTools.py b/Lib/fontTools/misc/encodingTools.py
index 454ec9ac..eccf951d 100644
--- a/Lib/fontTools/misc/encodingTools.py
+++ b/Lib/fontTools/misc/encodingTools.py
@@ -1,8 +1,6 @@
"""fontTools.misc.encodingTools.py -- tools for working with OpenType encodings.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import fontTools.encodings.codecs
# Map keyed by platformID, then platEncID, then possibly langID
diff --git a/Lib/fontTools/misc/etree.py b/Lib/fontTools/misc/etree.py
index 08e43f0a..6e943e4b 100644
--- a/Lib/fontTools/misc/etree.py
+++ b/Lib/fontTools/misc/etree.py
@@ -11,8 +11,7 @@ or subclasses built-in ElementTree classes to add features that are
only availble in lxml, like OrderedDict for attributes, pretty_print and
iterwalk.
"""
-from __future__ import absolute_import, unicode_literals
-from fontTools.misc.py23 import basestring, unicode, tounicode, open
+from fontTools.misc.py23 import unicode, tostr
XML_DECLARATION = """<?xml version='1.0' encoding='%s'?>"""
@@ -243,7 +242,7 @@ except ImportError:
Reject all bytes input that contains non-ASCII characters.
"""
try:
- s = tounicode(s, encoding="ascii", errors="strict")
+ s = tostr(s, encoding="ascii", errors="strict")
except UnicodeDecodeError:
raise ValueError(
"Bytes strings can only contain ASCII characters. "
@@ -357,7 +356,7 @@ except ImportError:
if isinstance(tag, QName):
if tag.text not in qnames:
add_qname(tag.text)
- elif isinstance(tag, basestring):
+ elif isinstance(tag, str):
if tag not in qnames:
add_qname(tag)
elif tag is not None and tag is not Comment and tag is not PI:
diff --git a/Lib/fontTools/misc/filenames.py b/Lib/fontTools/misc/filenames.py
index 260ace4c..0f010008 100644
--- a/Lib/fontTools/misc/filenames.py
+++ b/Lib/fontTools/misc/filenames.py
@@ -1,18 +1,21 @@
"""
-User name to file name conversion based on the UFO 3 spec:
-http://unifiedfontobject.org/versions/ufo3/conventions/
-
-The code was copied from:
-https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py
-
-Author: Tal Leming
-Copyright (c) 2005-2016, The RoboFab Developers:
- Erik van Blokland
- Tal Leming
- Just van Rossum
+This module implements the algorithm for converting between a "user name" -
+something that a user can choose arbitrarily inside a font editor - and a file
+name suitable for use in a wide range of operating systems and filesystems.
+
+The `UFO 3 specification <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
+provides an example of an algorithm for such conversion, which avoids illegal
+characters, reserved file names, ambiguity between upper- and lower-case
+characters, and clashes with existing files.
+
+This code was originally copied from
+`ufoLib <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
+by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
+
+- Erik van Blokland
+- Tal Leming
+- Just van Rossum
"""
-from __future__ import unicode_literals
-from fontTools.misc.py23 import basestring, unicode
illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
@@ -28,58 +31,73 @@ class NameTranslationError(Exception):
def userNameToFileName(userName, existing=[], prefix="", suffix=""):
+ """Converts from a user name to a file name.
+
+ Takes care to avoid illegal characters, reserved file names, ambiguity between
+ upper- and lower-case characters, and clashes with existing files.
+
+ Args:
+ userName (str): The input file name.
+ existing: A case-insensitive list of all existing file names.
+ prefix: Prefix to be prepended to the file name.
+ suffix: Suffix to be appended to the file name.
+
+ Returns:
+ A suitable filename.
+
+ Raises:
+ NameTranslationError: If no suitable name could be generated.
+
+ Examples::
+
+ >>> userNameToFileName("a") == "a"
+ True
+ >>> userNameToFileName("A") == "A_"
+ True
+ >>> userNameToFileName("AE") == "A_E_"
+ True
+ >>> userNameToFileName("Ae") == "A_e"
+ True
+ >>> userNameToFileName("ae") == "ae"
+ True
+ >>> userNameToFileName("aE") == "aE_"
+ True
+ >>> userNameToFileName("a.alt") == "a.alt"
+ True
+ >>> userNameToFileName("A.alt") == "A_.alt"
+ True
+ >>> userNameToFileName("A.Alt") == "A_.A_lt"
+ True
+ >>> userNameToFileName("A.aLt") == "A_.aL_t"
+ True
+ >>> userNameToFileName(u"A.alT") == "A_.alT_"
+ True
+ >>> userNameToFileName("T_H") == "T__H_"
+ True
+ >>> userNameToFileName("T_h") == "T__h"
+ True
+ >>> userNameToFileName("t_h") == "t_h"
+ True
+ >>> userNameToFileName("F_F_I") == "F__F__I_"
+ True
+ >>> userNameToFileName("f_f_i") == "f_f_i"
+ True
+ >>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
+ True
+ >>> userNameToFileName(".notdef") == "_notdef"
+ True
+ >>> userNameToFileName("con") == "_con"
+ True
+ >>> userNameToFileName("CON") == "C_O_N_"
+ True
+ >>> userNameToFileName("con.alt") == "_con.alt"
+ True
+ >>> userNameToFileName("alt.con") == "alt._con"
+ True
"""
- existing should be a case-insensitive list
- of all existing file names.
-
- >>> userNameToFileName("a") == "a"
- True
- >>> userNameToFileName("A") == "A_"
- True
- >>> userNameToFileName("AE") == "A_E_"
- True
- >>> userNameToFileName("Ae") == "A_e"
- True
- >>> userNameToFileName("ae") == "ae"
- True
- >>> userNameToFileName("aE") == "aE_"
- True
- >>> userNameToFileName("a.alt") == "a.alt"
- True
- >>> userNameToFileName("A.alt") == "A_.alt"
- True
- >>> userNameToFileName("A.Alt") == "A_.A_lt"
- True
- >>> userNameToFileName("A.aLt") == "A_.aL_t"
- True
- >>> userNameToFileName(u"A.alT") == "A_.alT_"
- True
- >>> userNameToFileName("T_H") == "T__H_"
- True
- >>> userNameToFileName("T_h") == "T__h"
- True
- >>> userNameToFileName("t_h") == "t_h"
- True
- >>> userNameToFileName("F_F_I") == "F__F__I_"
- True
- >>> userNameToFileName("f_f_i") == "f_f_i"
- True
- >>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
- True
- >>> userNameToFileName(".notdef") == "_notdef"
- True
- >>> userNameToFileName("con") == "_con"
- True
- >>> userNameToFileName("CON") == "C_O_N_"
- True
- >>> userNameToFileName("con.alt") == "_con.alt"
- True
- >>> userNameToFileName("alt.con") == "alt._con"
- True
- """
- # the incoming name must be a unicode string
- if not isinstance(userName, unicode):
- raise ValueError("The value for userName must be a unicode string.")
+ # the incoming name must be a str
+ if not isinstance(userName, str):
+ raise ValueError("The value for userName must be a string.")
# establish the prefix and suffix lengths
prefixLength = len(prefix)
suffixLength = len(suffix)
diff --git a/Lib/fontTools/misc/fixedTools.py b/Lib/fontTools/misc/fixedTools.py
index c5119abd..f0474abf 100644
--- a/Lib/fontTools/misc/fixedTools.py
+++ b/Lib/fontTools/misc/fixedTools.py
@@ -1,43 +1,139 @@
-"""fontTools.misc.fixedTools.py -- tools for working with fixed numbers.
"""
+The `OpenType specification <https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types>`_
+defines two fixed-point data types:
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-import math
+``Fixed``
+ A 32-bit signed fixed-point number with a 16 bit twos-complement
+ magnitude component and 16 fractional bits.
+``F2DOT14``
+ A 16-bit signed fixed-point number with a 2 bit twos-complement
+ magnitude component and 14 fractional bits.
+
+To support reading and writing data with these data types, this module provides
+functions for converting between fixed-point, float and string representations.
+
+.. data:: MAX_F2DOT14
+
+ The maximum value that can still fit in an F2Dot14. (1.99993896484375)
+"""
+
+from .roundTools import otRound
import logging
log = logging.getLogger(__name__)
__all__ = [
- "otRound",
+ "MAX_F2DOT14",
"fixedToFloat",
"floatToFixed",
- "floatToFixedToFloat",
+ "floatToFixedToFloat",
+ "floatToFixedToStr",
+ "fixedToStr",
+ "strToFixed",
+ "strToFixedToFloat",
"ensureVersionIsLong",
"versionToFixed",
]
-def otRound(value):
- """Round float value to nearest integer towards +Infinity.
- For fractional values of 0.5 and higher, take the next higher integer;
- for other fractional values, truncate.
+MAX_F2DOT14 = 0x7FFF / (1 << 14)
+
+
+def fixedToFloat(value, precisionBits):
+ """Converts a fixed-point number to a float given the number of
+ precision bits.
+
+ Args:
+ value (int): Number in fixed-point format.
+ precisionBits (int): Number of precision bits.
+
+ Returns:
+ Floating point value.
+
+ Examples::
- https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview
- https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
+ >>> import math
+ >>> f = fixedToFloat(-10139, precisionBits=14)
+ >>> math.isclose(f, -0.61883544921875)
+ True
"""
- return int(math.floor(value + 0.5))
+ return value / (1 << precisionBits)
-def fixedToFloat(value, precisionBits):
- """Converts a fixed-point number to a float, choosing the float
- that has the shortest decimal reprentation. Eg. to convert a
- fixed number in a 2.14 format, use precisionBits=14. This is
- pretty slow compared to a simple division. Use sporadically.
+def floatToFixed(value, precisionBits):
+ """Converts a float to a fixed-point number given the number of
+ precision bits.
+
+ Args:
+ value (float): Floating point value.
+ precisionBits (int): Number of precision bits.
+
+ Returns:
+ int: Fixed-point representation.
+
+ Examples::
+
+ >>> floatToFixed(-0.61883544921875, precisionBits=14)
+ -10139
+ >>> floatToFixed(-0.61884, precisionBits=14)
+ -10139
+ """
+ return otRound(value * (1 << precisionBits))
+
+
+def floatToFixedToFloat(value, precisionBits):
+ """Converts a float to a fixed-point number and back again.
+
+ By converting the float to fixed, rounding it, and converting it back
+ to float again, this returns a floating point values which is exactly
+ representable in fixed-point format.
+
+ Note: this **is** equivalent to ``fixedToFloat(floatToFixed(value))``.
+
+ Args:
+ value (float): The input floating point value.
+ precisionBits (int): Number of precision bits.
+
+ Returns:
+ float: The transformed and rounded value.
+
+ Examples::
+ >>> import math
+ >>> f1 = -0.61884
+ >>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14)
+ >>> f1 != f2
+ True
+ >>> math.isclose(f2, -0.61883544921875)
+ True
+ """
+ scale = 1 << precisionBits
+ return otRound(value * scale) / scale
+
+
+def fixedToStr(value, precisionBits):
+ """Converts a fixed-point number to a string representing a decimal float.
+
+ This chooses the float that has the shortest decimal representation (the least
+ number of fractional decimal digits).
+
+ For example, to convert a fixed-point number in a 2.14 format, use
+ ``precisionBits=14``::
+
+ >>> fixedToStr(-10139, precisionBits=14)
+ '-0.61884'
- precisionBits is only supported up to 16.
+ This is pretty slow compared to the simple division used in ``fixedToFloat``.
+ Use sporadically when you need to serialize or print the fixed-point number in
+ a human-readable form.
+
+ Args:
+ value (int): The fixed-point value to convert.
+ precisionBits (int): Number of precision bits, *up to a maximum of 16*.
+
+ Returns:
+ str: A string representation of the value.
"""
- if not value: return 0.0
+ if not value: return "0.0"
scale = 1 << precisionBits
value /= scale
@@ -46,7 +142,7 @@ def fixedToFloat(value, precisionBits):
hi = value + eps
# If the range of valid choices spans an integer, return the integer.
if int(lo) != int(hi):
- return float(round(value))
+ return str(float(round(value)))
fmt = "%.8f"
lo = fmt % lo
hi = fmt % hi
@@ -57,28 +153,99 @@ def fixedToFloat(value, precisionBits):
period = lo.find('.')
assert period < i
fmt = "%%.%df" % (i - period)
- value = fmt % value
- return float(value)
+ return fmt % value
-def floatToFixed(value, precisionBits):
- """Converts a float to a fixed-point number given the number of
- precisionBits. Ie. round(value * (1<<precisionBits)).
+
+def strToFixed(string, precisionBits):
+ """Converts a string representing a decimal float to a fixed-point number.
+
+ Args:
+ string (str): A string representing a decimal float.
+ precisionBits (int): Number of precision bits, *up to a maximum of 16*.
+
+ Returns:
+ int: Fixed-point representation.
+
+ Examples::
+
+ >>> ## to convert a float string to a 2.14 fixed-point number:
+ >>> strToFixed('-0.61884', precisionBits=14)
+ -10139
"""
- return otRound(value * (1<<precisionBits))
+ value = float(string)
+ return otRound(value * (1 << precisionBits))
-def floatToFixedToFloat(value, precisionBits):
- """Converts a float to a fixed-point number given the number of
- precisionBits, round it, then convert it back to float again.
- Ie. round(value * (1<<precisionBits)) / (1<<precisionBits)
- Note: this is *not* equivalent to fixedToFloat(floatToFixed(value)),
- which would return the shortest representation of the rounded value.
+
+def strToFixedToFloat(string, precisionBits):
+ """Convert a string to a decimal float with fixed-point rounding.
+
+ This first converts string to a float, then turns it into a fixed-point
+ number with ``precisionBits`` fractional binary digits, then back to a
+ float again.
+
+ This is simply a shorthand for fixedToFloat(floatToFixed(float(s))).
+
+ Args:
+ string (str): A string representing a decimal float.
+ precisionBits (int): Number of precision bits.
+
+ Returns:
+ float: The transformed and rounded value.
+
+ Examples::
+
+ >>> import math
+ >>> s = '-0.61884'
+ >>> bits = 14
+ >>> f = strToFixedToFloat(s, precisionBits=bits)
+ >>> math.isclose(f, -0.61883544921875)
+ True
+ >>> f == fixedToFloat(floatToFixed(float(s), precisionBits=bits), precisionBits=bits)
+ True
"""
- scale = 1<<precisionBits
+ value = float(string)
+ scale = 1 << precisionBits
return otRound(value * scale) / scale
+
+def floatToFixedToStr(value, precisionBits):
+ """Convert float to string with fixed-point rounding.
+
+ This uses the shortest decimal representation (ie. the least
+ number of fractional decimal digits) to represent the equivalent
+ fixed-point number with ``precisionBits`` fractional binary digits.
+ It uses fixedToStr under the hood.
+
+ >>> floatToFixedToStr(-0.61883544921875, precisionBits=14)
+ '-0.61884'
+
+ Args:
+ value (float): The float value to convert.
+ precisionBits (int): Number of precision bits, *up to a maximum of 16*.
+
+ Returns:
+ str: A string representation of the value.
+
+ """
+ fixed = otRound(value * (1 << precisionBits))
+ return fixedToStr(fixed, precisionBits)
+
+
def ensureVersionIsLong(value):
- """Ensure a table version is an unsigned long (unsigned short major,
- unsigned short minor) instead of a float."""
+ """Ensure a table version is an unsigned long.
+
+ OpenType table version numbers are expressed as a single unsigned long
+ comprising of an unsigned short major version and unsigned short minor
+ version. This function detects if the value to be used as a version number
+ looks too small (i.e. is less than ``0x10000``), and converts it to
+ fixed-point using :func:`floatToFixed` if so.
+
+ Args:
+ value (Number): a candidate table version number.
+
+ Returns:
+ int: A table version number, possibly corrected to fixed-point.
+ """
if value < 0x10000:
newValue = floatToFixed(value, 16)
log.warning(
@@ -89,7 +256,14 @@ def ensureVersionIsLong(value):
def versionToFixed(value):
- """Converts a table version to a fixed"""
+ """Ensure a table version number is fixed-point.
+
+ Args:
+ value (str): a candidate table version number.
+
+ Returns:
+ int: A table version number, possibly corrected to fixed-point.
+ """
value = int(value, 0) if value.startswith("0") else float(value)
value = ensureVersionIsLong(value)
return value
diff --git a/Lib/fontTools/misc/intTools.py b/Lib/fontTools/misc/intTools.py
index 9eb2f0f9..448e1627 100644
--- a/Lib/fontTools/misc/intTools.py
+++ b/Lib/fontTools/misc/intTools.py
@@ -1,13 +1,23 @@
-"""Misc integer tools."""
-
-from __future__ import print_function, absolute_import, division
-from fontTools.misc.py23 import *
-
__all__ = ['popCount']
def popCount(v):
- """Return number of 1 bits in an integer."""
+ """Return number of 1 bits (population count) of an integer.
+
+ If the integer is negative, the number of 1 bits in the
+ twos-complement representation of the integer is returned. i.e.
+ ``popCount(-30) == 28`` because -30 is::
+
+ 1111 1111 1111 1111 1111 1111 1110 0010
+
+ Uses the algorithm from `HAKMEM item 169 <https://www.inwap.com/pdp10/hbaker/hakmem/hacks.html#item169>`_.
+
+ Args:
+ v (int): Value to count.
+
+ Returns:
+ Number of 1 bits in the binary representation of ``v``.
+ """
if v > 0xFFFFFFFF:
return popCount(v >> 32) + popCount(v & 0xFFFFFFFF)
diff --git a/Lib/fontTools/misc/loggingTools.py b/Lib/fontTools/misc/loggingTools.py
index b4b9a9b0..d1baa839 100644
--- a/Lib/fontTools/misc/loggingTools.py
+++ b/Lib/fontTools/misc/loggingTools.py
@@ -1,23 +1,10 @@
-""" fontTools.misc.loggingTools.py -- tools for interfacing with the Python
-logging package.
-"""
-
-from __future__ import print_function, absolute_import
-from fontTools.misc.py23 import *
import sys
import logging
import timeit
from functools import wraps
-try:
- from collections.abc import Mapping, Callable
-except ImportError: # python < 3.3
- from collections import Mapping, Callable
+from collections.abc import Mapping, Callable
import warnings
-
-try:
- from logging import PercentStyle
-except ImportError:
- PercentStyle = None
+from logging import PercentStyle
# default logging level used by Timer class
@@ -33,10 +20,18 @@ DEFAULT_FORMATS = {
class LevelFormatter(logging.Formatter):
- """ Formatter class which optionally takes a dict of logging levels to
+ """Log formatter with level-specific formatting.
+
+ Formatter class which optionally takes a dict of logging levels to
format strings, allowing to customise the log records appearance for
specific levels.
- The '*' key identifies the default format string.
+
+
+ Attributes:
+ fmt: A dictionary mapping logging levels to format strings.
+ The ``*`` key identifies the default format string.
+ datefmt: As per py:class:`logging.Formatter`
+ style: As per py:class:`logging.Formatter`
>>> import sys
>>> handler = logging.StreamHandler(sys.stdout)
@@ -64,7 +59,7 @@ class LevelFormatter(logging.Formatter):
"only '%' percent style is supported in both python 2 and 3")
if fmt is None:
fmt = DEFAULT_FORMATS
- if isinstance(fmt, basestring):
+ if isinstance(fmt, str):
default_format = fmt
custom_formats = {}
elif isinstance(fmt, Mapping):
@@ -91,46 +86,48 @@ class LevelFormatter(logging.Formatter):
def configLogger(**kwargs):
- """ Do basic configuration for the logging system. This is more or less
- the same as logging.basicConfig with some additional options and defaults.
+ """A more sophisticated logging system configuation manager.
- The default behaviour is to create a StreamHandler which writes to
- sys.stderr, set a formatter using the DEFAULT_FORMATS strings, and add
+ This is more or less the same as :py:func:`logging.basicConfig`,
+ with some additional options and defaults.
+
+ The default behaviour is to create a ``StreamHandler`` which writes to
+ sys.stderr, set a formatter using the ``DEFAULT_FORMATS`` strings, and add
the handler to the top-level library logger ("fontTools").
A number of optional keyword arguments may be specified, which can alter
the default behaviour.
- logger Specifies the logger name or a Logger instance to be configured.
- (it defaults to "fontTools" logger). Unlike basicConfig, this
- function can be called multiple times to reconfigure a logger.
- If the logger or any of its children already exists before the
- call is made, they will be reset before the new configuration
- is applied.
- filename Specifies that a FileHandler be created, using the specified
- filename, rather than a StreamHandler.
- filemode Specifies the mode to open the file, if filename is specified
- (if filemode is unspecified, it defaults to 'a').
- format Use the specified format string for the handler. This argument
- also accepts a dictionary of format strings keyed by level name,
- to allow customising the records appearance for specific levels.
- The special '*' key is for 'any other' level.
- datefmt Use the specified date/time format.
- level Set the logger level to the specified level.
- stream Use the specified stream to initialize the StreamHandler. Note
- that this argument is incompatible with 'filename' - if both
- are present, 'stream' is ignored.
- handlers If specified, this should be an iterable of already created
- handlers, which will be added to the logger. Any handler
- in the list which does not have a formatter assigned will be
- assigned the formatter created in this function.
- filters If specified, this should be an iterable of already created
- filters, which will be added to the handler(s), if the latter
- do(es) not already have filters assigned.
- propagate All loggers have a "propagate" attribute initially set to True,
- which determines whether to continue searching for handlers up
- the logging hierarchy. By default, this arguments sets the
- "propagate" attribute to False.
+ Args:
+
+ logger: Specifies the logger name or a Logger instance to be
+ configured. (Defaults to "fontTools" logger). Unlike ``basicConfig``,
+ this function can be called multiple times to reconfigure a logger.
+ If the logger or any of its children already exists before the call is
+ made, they will be reset before the new configuration is applied.
+ filename: Specifies that a ``FileHandler`` be created, using the
+ specified filename, rather than a ``StreamHandler``.
+ filemode: Specifies the mode to open the file, if filename is
+ specified. (If filemode is unspecified, it defaults to ``a``).
+ format: Use the specified format string for the handler. This
+ argument also accepts a dictionary of format strings keyed by
+ level name, to allow customising the records appearance for
+ specific levels. The special ``'*'`` key is for 'any other' level.
+ datefmt: Use the specified date/time format.
+ level: Set the logger level to the specified level.
+ stream: Use the specified stream to initialize the StreamHandler. Note
+ that this argument is incompatible with ``filename`` - if both
+ are present, ``stream`` is ignored.
+ handlers: If specified, this should be an iterable of already created
+ handlers, which will be added to the logger. Any handler in the
+ list which does not have a formatter assigned will be assigned the
+ formatter created in this function.
+ filters: If specified, this should be an iterable of already created
+ filters. If the ``handlers`` do not already have filters assigned,
+ these filters will be added to them.
+ propagate: All loggers have a ``propagate`` attribute which determines
+ whether to continue searching for handlers up the logging hierarchy.
+ If not provided, the "propagate" attribute will be set to ``False``.
"""
# using kwargs to enforce keyword-only arguments in py2.
handlers = kwargs.pop("handlers", None)
@@ -153,7 +150,7 @@ def configLogger(**kwargs):
handlers = [h]
# By default, the top-level library logger is configured.
logger = kwargs.pop("logger", "fontTools")
- if not logger or isinstance(logger, basestring):
+ if not logger or isinstance(logger, str):
# empty "" or None means the 'root' logger
logger = logging.getLogger(logger)
# before (re)configuring, reset named logger and its children (if exist)
@@ -390,9 +387,11 @@ class Timer(object):
class ChannelsFilter(logging.Filter):
- """ Filter out records emitted from a list of enabled channel names,
- including their children. It works the same as the logging.Filter class,
- but allows to specify multiple channel names.
+ """Provides a hierarchical filter for log entries based on channel names.
+
+ Filters out records emitted from a list of enabled channel names,
+ including their children. It works the same as the ``logging.Filter``
+ class, but allows the user to specify multiple channel names.
>>> import sys
>>> handler = logging.StreamHandler(sys.stdout)
@@ -417,13 +416,13 @@ class ChannelsFilter(logging.Filter):
def __init__(self, *names):
self.names = names
self.num = len(names)
- self.lenghts = {n: len(n) for n in names}
+ self.lengths = {n: len(n) for n in names}
def filter(self, record):
if self.num == 0:
return True
for name in self.names:
- nlen = self.lenghts[name]
+ nlen = self.lengths[name]
if name == record.name:
return True
elif (record.name.find(name, 0, nlen) == 0
@@ -436,7 +435,7 @@ class CapturingLogHandler(logging.Handler):
def __init__(self, logger, level):
super(CapturingLogHandler, self).__init__(level=level)
self.records = []
- if isinstance(logger, basestring):
+ if isinstance(logger, str):
self.logger = logging.getLogger(logger)
else:
self.logger = logger
@@ -477,10 +476,12 @@ class CapturingLogHandler(logging.Handler):
class LogMixin(object):
""" Mixin class that adds logging functionality to another class.
- You can define a new class that subclasses from LogMixin as well as
+
+ You can define a new class that subclasses from ``LogMixin`` as well as
other base classes through multiple inheritance.
- All instances of that class will have a 'log' property that returns
- a logging.Logger named after their respective <module>.<class>.
+ All instances of that class will have a ``log`` property that returns
+ a ``logging.Logger`` named after their respective ``<module>.<class>``.
+
For example:
>>> class BaseClass(object):
diff --git a/Lib/fontTools/misc/macCreatorType.py b/Lib/fontTools/misc/macCreatorType.py
index 2b33e898..fb237200 100644
--- a/Lib/fontTools/misc/macCreatorType.py
+++ b/Lib/fontTools/misc/macCreatorType.py
@@ -1,14 +1,8 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-import sys
+from fontTools.misc.py23 import Tag, bytesjoin, strjoin
try:
import xattr
except ImportError:
xattr = None
-try:
- import MacOS
-except ImportError:
- MacOS = None
def _reverseString(s):
@@ -18,6 +12,16 @@ def _reverseString(s):
def getMacCreatorAndType(path):
+ """Returns file creator and file type codes for a path.
+
+ Args:
+ path (str): A file path.
+
+ Returns:
+ A tuple of two :py:class:`fontTools.py23.Tag` objects, the first
+ representing the file creator and the second representing the
+ file type.
+ """
if xattr is not None:
try:
finderInfo = xattr.getxattr(path, 'com.apple.FinderInfo')
@@ -27,25 +31,24 @@ def getMacCreatorAndType(path):
fileType = Tag(finderInfo[:4])
fileCreator = Tag(finderInfo[4:8])
return fileCreator, fileType
- if MacOS is not None:
- fileCreator, fileType = MacOS.GetCreatorAndType(path)
- if sys.version_info[:2] < (2, 7) and sys.byteorder == "little":
- # work around bug in MacOS.GetCreatorAndType() on intel:
- # http://bugs.python.org/issue1594
- # (fixed with Python 2.7)
- fileCreator = _reverseString(fileCreator)
- fileType = _reverseString(fileType)
- return fileCreator, fileType
- else:
- return None, None
+ return None, None
def setMacCreatorAndType(path, fileCreator, fileType):
+ """Set file creator and file type codes for a path.
+
+ Note that if the ``xattr`` module is not installed, no action is
+ taken but no error is raised.
+
+ Args:
+ path (str): A file path.
+ fileCreator: A four-character file creator tag.
+ fileType: A four-character file type tag.
+
+ """
if xattr is not None:
from fontTools.misc.textTools import pad
if not all(len(s) == 4 for s in (fileCreator, fileType)):
raise TypeError('arg must be string of 4 chars')
finderInfo = pad(bytesjoin([fileType, fileCreator]), 32)
xattr.setxattr(path, 'com.apple.FinderInfo', finderInfo)
- if MacOS is not None:
- MacOS.SetCreatorAndType(path, fileCreator, fileType)
diff --git a/Lib/fontTools/misc/macRes.py b/Lib/fontTools/misc/macRes.py
index db832ec5..2c15b347 100644
--- a/Lib/fontTools/misc/macRes.py
+++ b/Lib/fontTools/misc/macRes.py
@@ -1,13 +1,9 @@
-""" Tools for reading Mac resource forks. """
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, tostr
+from io import BytesIO
import struct
from fontTools.misc import sstruct
from collections import OrderedDict
-try:
- from collections.abc import MutableMapping
-except ImportError:
- from UserDict import DictMixin as MutableMapping
+from collections.abc import MutableMapping
class ResourceError(Exception):
@@ -15,8 +11,25 @@ class ResourceError(Exception):
class ResourceReader(MutableMapping):
+ """Reader for Mac OS resource forks.
+ Parses a resource fork and returns resources according to their type.
+ If run on OS X, this will open the resource fork in the filesystem.
+ Otherwise, it will open the file itself and attempt to read it as
+ though it were a resource fork.
+
+ The returned object can be indexed by type and iterated over,
+ returning in each case a list of py:class:`Resource` objects
+ representing all the resources of a certain type.
+
+ """
def __init__(self, fileOrPath):
+ """Open a file
+
+ Args:
+ fileOrPath: Either an object supporting a ``read`` method, an
+ ``os.PathLike`` object, or a string.
+ """
self._resources = OrderedDict()
if hasattr(fileOrPath, 'read'):
self.file = fileOrPath
@@ -125,6 +138,7 @@ class ResourceReader(MutableMapping):
@property
def types(self):
+ """A list of the types of resources in the resource fork."""
return list(self._resources.keys())
def countResources(self, resType):
@@ -135,6 +149,7 @@ class ResourceReader(MutableMapping):
return 0
def getIndices(self, resType):
+ """Returns a list of indices of resources of a given type."""
numRes = self.countResources(resType)
if numRes:
return list(range(1, numRes+1))
@@ -171,6 +186,15 @@ class ResourceReader(MutableMapping):
class Resource(object):
+ """Represents a resource stored within a resource fork.
+
+ Attributes:
+ type: resource type.
+ data: resource data.
+ id: ID.
+ name: resource name.
+ attr: attributes.
+ """
def __init__(self, resType=None, resData=None, resID=None, resName=None,
resAttr=None):
diff --git a/Lib/fontTools/misc/plistlib.py b/Lib/fontTools/misc/plistlib.py
deleted file mode 100644
index a0e1003f..00000000
--- a/Lib/fontTools/misc/plistlib.py
+++ /dev/null
@@ -1,545 +0,0 @@
-from __future__ import absolute_import, unicode_literals
-import sys
-import re
-from io import BytesIO
-from datetime import datetime
-from base64 import b64encode, b64decode
-from numbers import Integral
-
-try:
- from collections.abc import Mapping # python >= 3.3
-except ImportError:
- from collections import Mapping
-
-try:
- from functools import singledispatch
-except ImportError:
- try:
- from singledispatch import singledispatch
- except ImportError:
- singledispatch = None
-
-from fontTools.misc import etree
-
-from fontTools.misc.py23 import (
- unicode,
- basestring,
- tounicode,
- tobytes,
- SimpleNamespace,
- range,
-)
-
-# On python3, by default we deserialize <data> elements as bytes, whereas on
-# python2 we deserialize <data> elements as plistlib.Data objects, in order
-# to distinguish them from the built-in str type (which is bytes on python2).
-# Similarly, by default on python3 we serialize bytes as <data> elements;
-# however, on python2 we serialize bytes as <string> elements (they must
-# only contain ASCII characters in this case).
-# You can pass use_builtin_types=[True|False] to load/dump etc. functions to
-# enforce the same treatment of bytes across python 2 and 3.
-# NOTE that unicode type always maps to <string> element, and plistlib.Data
-# always maps to <data> element, regardless of use_builtin_types.
-PY3 = sys.version_info[0] > 2
-if PY3:
- USE_BUILTIN_TYPES = True
-else:
- USE_BUILTIN_TYPES = False
-
-XML_DECLARATION = b"""<?xml version='1.0' encoding='UTF-8'?>"""
-
-PLIST_DOCTYPE = (
- b'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" '
- b'"http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
-)
-
-# Date should conform to a subset of ISO 8601:
-# YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'
-_date_parser = re.compile(
- r"(?P<year>\d\d\d\d)"
- r"(?:-(?P<month>\d\d)"
- r"(?:-(?P<day>\d\d)"
- r"(?:T(?P<hour>\d\d)"
- r"(?::(?P<minute>\d\d)"
- r"(?::(?P<second>\d\d))"
- r"?)?)?)?)?Z",
- getattr(re, "ASCII", 0), # py3-only
-)
-
-
-def _date_from_string(s):
- order = ("year", "month", "day", "hour", "minute", "second")
- gd = _date_parser.match(s).groupdict()
- lst = []
- for key in order:
- val = gd[key]
- if val is None:
- break
- lst.append(int(val))
- return datetime(*lst)
-
-
-def _date_to_string(d):
- return "%04d-%02d-%02dT%02d:%02d:%02dZ" % (
- d.year,
- d.month,
- d.day,
- d.hour,
- d.minute,
- d.second,
- )
-
-
-def _encode_base64(data, maxlinelength=76, indent_level=1):
- data = b64encode(data)
- if data and maxlinelength:
- # split into multiple lines right-justified to 'maxlinelength' chars
- indent = b"\n" + b" " * indent_level
- max_length = max(16, maxlinelength - len(indent))
- chunks = []
- for i in range(0, len(data), max_length):
- chunks.append(indent)
- chunks.append(data[i : i + max_length])
- chunks.append(indent)
- data = b"".join(chunks)
- return data
-
-
-class Data:
- """Wrapper for binary data returned in place of the built-in bytes type
- when loading property list data with use_builtin_types=False.
- """
-
- def __init__(self, data):
- if not isinstance(data, bytes):
- raise TypeError("Expected bytes, found %s" % type(data).__name__)
- self.data = data
-
- @classmethod
- def fromBase64(cls, data):
- return cls(b64decode(data))
-
- def asBase64(self, maxlinelength=76, indent_level=1):
- return _encode_base64(
- self.data, maxlinelength=maxlinelength, indent_level=indent_level
- )
-
- def __eq__(self, other):
- if isinstance(other, self.__class__):
- return self.data == other.data
- elif isinstance(other, bytes):
- return self.data == other
- else:
- return NotImplemented
-
- def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, repr(self.data))
-
-
-class PlistTarget(object):
- """ Event handler using the ElementTree Target API that can be
- passed to a XMLParser to produce property list objects from XML.
- It is based on the CPython plistlib module's _PlistParser class,
- but does not use the expat parser.
-
- >>> from fontTools.misc import etree
- >>> parser = etree.XMLParser(target=PlistTarget())
- >>> result = etree.XML(
- ... "<dict>"
- ... " <key>something</key>"
- ... " <string>blah</string>"
- ... "</dict>",
- ... parser=parser)
- >>> result == {"something": "blah"}
- True
-
- Links:
- https://github.com/python/cpython/blob/master/Lib/plistlib.py
- http://lxml.de/parsing.html#the-target-parser-interface
- """
-
- def __init__(self, use_builtin_types=None, dict_type=dict):
- self.stack = []
- self.current_key = None
- self.root = None
- if use_builtin_types is None:
- self._use_builtin_types = USE_BUILTIN_TYPES
- else:
- self._use_builtin_types = use_builtin_types
- self._dict_type = dict_type
-
- def start(self, tag, attrib):
- self._data = []
- handler = _TARGET_START_HANDLERS.get(tag)
- if handler is not None:
- handler(self)
-
- def end(self, tag):
- handler = _TARGET_END_HANDLERS.get(tag)
- if handler is not None:
- handler(self)
-
- def data(self, data):
- self._data.append(data)
-
- def close(self):
- return self.root
-
- # helpers
-
- def add_object(self, value):
- if self.current_key is not None:
- if not isinstance(self.stack[-1], type({})):
- raise ValueError("unexpected element: %r" % self.stack[-1])
- self.stack[-1][self.current_key] = value
- self.current_key = None
- elif not self.stack:
- # this is the root object
- self.root = value
- else:
- if not isinstance(self.stack[-1], type([])):
- raise ValueError("unexpected element: %r" % self.stack[-1])
- self.stack[-1].append(value)
-
- def get_data(self):
- data = "".join(self._data)
- self._data = []
- return data
-
-
-# event handlers
-
-
-def start_dict(self):
- d = self._dict_type()
- self.add_object(d)
- self.stack.append(d)
-
-
-def end_dict(self):
- if self.current_key:
- raise ValueError("missing value for key '%s'" % self.current_key)
- self.stack.pop()
-
-
-def end_key(self):
- if self.current_key or not isinstance(self.stack[-1], type({})):
- raise ValueError("unexpected key")
- self.current_key = self.get_data()
-
-
-def start_array(self):
- a = []
- self.add_object(a)
- self.stack.append(a)
-
-
-def end_array(self):
- self.stack.pop()
-
-
-def end_true(self):
- self.add_object(True)
-
-
-def end_false(self):
- self.add_object(False)
-
-
-def end_integer(self):
- self.add_object(int(self.get_data()))
-
-
-def end_real(self):
- self.add_object(float(self.get_data()))
-
-
-def end_string(self):
- self.add_object(self.get_data())
-
-
-def end_data(self):
- if self._use_builtin_types:
- self.add_object(b64decode(self.get_data()))
- else:
- self.add_object(Data.fromBase64(self.get_data()))
-
-
-def end_date(self):
- self.add_object(_date_from_string(self.get_data()))
-
-
-_TARGET_START_HANDLERS = {"dict": start_dict, "array": start_array}
-
-_TARGET_END_HANDLERS = {
- "dict": end_dict,
- "array": end_array,
- "key": end_key,
- "true": end_true,
- "false": end_false,
- "integer": end_integer,
- "real": end_real,
- "string": end_string,
- "data": end_data,
- "date": end_date,
-}
-
-
-# functions to build element tree from plist data
-
-
-def _string_element(value, ctx):
- el = etree.Element("string")
- el.text = value
- return el
-
-
-def _bool_element(value, ctx):
- if value:
- return etree.Element("true")
- else:
- return etree.Element("false")
-
-
-def _integer_element(value, ctx):
- if -1 << 63 <= value < 1 << 64:
- el = etree.Element("integer")
- el.text = "%d" % value
- return el
- else:
- raise OverflowError(value)
-
-
-def _real_element(value, ctx):
- el = etree.Element("real")
- el.text = repr(value)
- return el
-
-
-def _dict_element(d, ctx):
- el = etree.Element("dict")
- items = d.items()
- if ctx.sort_keys:
- items = sorted(items)
- ctx.indent_level += 1
- for key, value in items:
- if not isinstance(key, basestring):
- if ctx.skipkeys:
- continue
- raise TypeError("keys must be strings")
- k = etree.SubElement(el, "key")
- k.text = tounicode(key, "utf-8")
- el.append(_make_element(value, ctx))
- ctx.indent_level -= 1
- return el
-
-
-def _array_element(array, ctx):
- el = etree.Element("array")
- if len(array) == 0:
- return el
- ctx.indent_level += 1
- for value in array:
- el.append(_make_element(value, ctx))
- ctx.indent_level -= 1
- return el
-
-
-def _date_element(date, ctx):
- el = etree.Element("date")
- el.text = _date_to_string(date)
- return el
-
-
-def _data_element(data, ctx):
- el = etree.Element("data")
- el.text = _encode_base64(
- data,
- maxlinelength=(76 if ctx.pretty_print else None),
- indent_level=ctx.indent_level,
- )
- return el
-
-
-def _string_or_data_element(raw_bytes, ctx):
- if ctx.use_builtin_types:
- return _data_element(raw_bytes, ctx)
- else:
- try:
- string = raw_bytes.decode(encoding="ascii", errors="strict")
- except UnicodeDecodeError:
- raise ValueError(
- "invalid non-ASCII bytes; use unicode string instead: %r"
- % raw_bytes
- )
- return _string_element(string, ctx)
-
-
-# if singledispatch is available, we use a generic '_make_element' function
-# and register overloaded implementations that are run based on the type of
-# the first argument
-
-if singledispatch is not None:
-
- @singledispatch
- def _make_element(value, ctx):
- raise TypeError("unsupported type: %s" % type(value))
-
- _make_element.register(unicode)(_string_element)
- _make_element.register(bool)(_bool_element)
- _make_element.register(Integral)(_integer_element)
- _make_element.register(float)(_real_element)
- _make_element.register(Mapping)(_dict_element)
- _make_element.register(list)(_array_element)
- _make_element.register(tuple)(_array_element)
- _make_element.register(datetime)(_date_element)
- _make_element.register(bytes)(_string_or_data_element)
- _make_element.register(bytearray)(_data_element)
- _make_element.register(Data)(lambda v, ctx: _data_element(v.data, ctx))
-
-else:
- # otherwise we use a long switch-like if statement
-
- def _make_element(value, ctx):
- if isinstance(value, unicode):
- return _string_element(value, ctx)
- elif isinstance(value, bool):
- return _bool_element(value, ctx)
- elif isinstance(value, Integral):
- return _integer_element(value, ctx)
- elif isinstance(value, float):
- return _real_element(value, ctx)
- elif isinstance(value, Mapping):
- return _dict_element(value, ctx)
- elif isinstance(value, (list, tuple)):
- return _array_element(value, ctx)
- elif isinstance(value, datetime):
- return _date_element(value, ctx)
- elif isinstance(value, bytes):
- return _string_or_data_element(value, ctx)
- elif isinstance(value, bytearray):
- return _data_element(value, ctx)
- elif isinstance(value, Data):
- return _data_element(value.data, ctx)
-
-
-# Public functions to create element tree from plist-compatible python
-# data structures and viceversa, for use when (de)serializing GLIF xml.
-
-
-def totree(
- value,
- sort_keys=True,
- skipkeys=False,
- use_builtin_types=None,
- pretty_print=True,
- indent_level=1,
-):
- if use_builtin_types is None:
- use_builtin_types = USE_BUILTIN_TYPES
- else:
- use_builtin_types = use_builtin_types
- context = SimpleNamespace(
- sort_keys=sort_keys,
- skipkeys=skipkeys,
- use_builtin_types=use_builtin_types,
- pretty_print=pretty_print,
- indent_level=indent_level,
- )
- return _make_element(value, context)
-
-
-def fromtree(tree, use_builtin_types=None, dict_type=dict):
- target = PlistTarget(
- use_builtin_types=use_builtin_types, dict_type=dict_type
- )
- for action, element in etree.iterwalk(tree, events=("start", "end")):
- if action == "start":
- target.start(element.tag, element.attrib)
- elif action == "end":
- # if there are no children, parse the leaf's data
- if not len(element):
- # always pass str, not None
- target.data(element.text or "")
- target.end(element.tag)
- return target.close()
-
-
-# python3 plistlib API
-
-
-def load(fp, use_builtin_types=None, dict_type=dict):
- if not hasattr(fp, "read"):
- raise AttributeError(
- "'%s' object has no attribute 'read'" % type(fp).__name__
- )
- target = PlistTarget(
- use_builtin_types=use_builtin_types, dict_type=dict_type
- )
- parser = etree.XMLParser(target=target)
- result = etree.parse(fp, parser=parser)
- # lxml returns the target object directly, while ElementTree wraps
- # it as the root of an ElementTree object
- try:
- return result.getroot()
- except AttributeError:
- return result
-
-
-def loads(value, use_builtin_types=None, dict_type=dict):
- fp = BytesIO(value)
- return load(fp, use_builtin_types=use_builtin_types, dict_type=dict_type)
-
-
-def dump(
- value,
- fp,
- sort_keys=True,
- skipkeys=False,
- use_builtin_types=None,
- pretty_print=True,
-):
- if not hasattr(fp, "write"):
- raise AttributeError(
- "'%s' object has no attribute 'write'" % type(fp).__name__
- )
- root = etree.Element("plist", version="1.0")
- el = totree(
- value,
- sort_keys=sort_keys,
- skipkeys=skipkeys,
- use_builtin_types=use_builtin_types,
- pretty_print=pretty_print,
- )
- root.append(el)
- tree = etree.ElementTree(root)
- # we write the doctype ourselves instead of using the 'doctype' argument
- # of 'write' method, becuse lxml will force adding a '\n' even when
- # pretty_print is False.
- if pretty_print:
- header = b"\n".join((XML_DECLARATION, PLIST_DOCTYPE, b""))
- else:
- header = XML_DECLARATION + PLIST_DOCTYPE
- fp.write(header)
- tree.write(
- fp, encoding="utf-8", pretty_print=pretty_print, xml_declaration=False
- )
-
-
-def dumps(
- value,
- sort_keys=True,
- skipkeys=False,
- use_builtin_types=None,
- pretty_print=True,
-):
- fp = BytesIO()
- dump(
- value,
- fp,
- sort_keys=sort_keys,
- skipkeys=skipkeys,
- use_builtin_types=use_builtin_types,
- pretty_print=pretty_print,
- )
- return fp.getvalue()
diff --git a/Lib/fontTools/misc/plistlib/__init__.py b/Lib/fontTools/misc/plistlib/__init__.py
new file mode 100644
index 00000000..84dc4183
--- /dev/null
+++ b/Lib/fontTools/misc/plistlib/__init__.py
@@ -0,0 +1,677 @@
+import collections.abc
+import re
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ List,
+ Mapping,
+ MutableMapping,
+ Optional,
+ Sequence,
+ Type,
+ Union,
+ IO,
+)
+import warnings
+from io import BytesIO
+from datetime import datetime
+from base64 import b64encode, b64decode
+from numbers import Integral
+from types import SimpleNamespace
+from functools import singledispatch
+
+from fontTools.misc import etree
+
+from fontTools.misc.py23 import tostr
+
+
+# By default, we
+# - deserialize <data> elements as bytes and
+# - serialize bytes as <data> elements.
+# Before, on Python 2, we
+# - deserialized <data> elements as plistlib.Data objects, in order to
+# distinguish them from the built-in str type (which is bytes on python2)
+# - serialized bytes as <string> elements (they must have only contained
+# ASCII characters in this case)
+# You can pass use_builtin_types=[True|False] to the load/dump etc. functions
+# to enforce a specific treatment.
+# NOTE that unicode type always maps to <string> element, and plistlib.Data
+# always maps to <data> element, regardless of use_builtin_types.
+USE_BUILTIN_TYPES = True
+
+XML_DECLARATION = b"""<?xml version='1.0' encoding='UTF-8'?>"""
+
+PLIST_DOCTYPE = (
+ b'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" '
+ b'"http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
+)
+
+
+# Date should conform to a subset of ISO 8601:
+# YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'
+_date_parser = re.compile(
+ r"(?P<year>\d\d\d\d)"
+ r"(?:-(?P<month>\d\d)"
+ r"(?:-(?P<day>\d\d)"
+ r"(?:T(?P<hour>\d\d)"
+ r"(?::(?P<minute>\d\d)"
+ r"(?::(?P<second>\d\d))"
+ r"?)?)?)?)?Z",
+ re.ASCII,
+)
+
+
+def _date_from_string(s: str) -> datetime:
+ order = ("year", "month", "day", "hour", "minute", "second")
+ m = _date_parser.match(s)
+ if m is None:
+ raise ValueError(f"Expected ISO 8601 date string, but got '{s:r}'.")
+ gd = m.groupdict()
+ lst = []
+ for key in order:
+ val = gd[key]
+ if val is None:
+ break
+ lst.append(int(val))
+ # NOTE: mypy doesn't know that lst is 6 elements long.
+ return datetime(*lst) # type:ignore
+
+
+def _date_to_string(d: datetime) -> str:
+ return "%04d-%02d-%02dT%02d:%02d:%02dZ" % (
+ d.year,
+ d.month,
+ d.day,
+ d.hour,
+ d.minute,
+ d.second,
+ )
+
+
+class Data:
+ """Represents binary data when ``use_builtin_types=False.``
+
+ This class wraps binary data loaded from a plist file when the
+ ``use_builtin_types`` argument to the loading function (:py:func:`fromtree`,
+ :py:func:`load`, :py:func:`loads`) is false.
+
+ The actual binary data is retrieved using the ``data`` attribute.
+ """
+
+ def __init__(self, data: bytes) -> None:
+ if not isinstance(data, bytes):
+ raise TypeError("Expected bytes, found %s" % type(data).__name__)
+ self.data = data
+
+ @classmethod
+ def fromBase64(cls, data: Union[bytes, str]) -> "Data":
+ return cls(b64decode(data))
+
+ def asBase64(self, maxlinelength: int = 76, indent_level: int = 1) -> bytes:
+ return _encode_base64(
+ self.data, maxlinelength=maxlinelength, indent_level=indent_level
+ )
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, self.__class__):
+ return self.data == other.data
+ elif isinstance(other, bytes):
+ return self.data == other
+ else:
+ return NotImplemented
+
+ def __repr__(self) -> str:
+ return "%s(%s)" % (self.__class__.__name__, repr(self.data))
+
+
+def _encode_base64(
+ data: bytes, maxlinelength: Optional[int] = 76, indent_level: int = 1
+) -> bytes:
+ data = b64encode(data)
+ if data and maxlinelength:
+ # split into multiple lines right-justified to 'maxlinelength' chars
+ indent = b"\n" + b" " * indent_level
+ max_length = max(16, maxlinelength - len(indent))
+ chunks = []
+ for i in range(0, len(data), max_length):
+ chunks.append(indent)
+ chunks.append(data[i : i + max_length])
+ chunks.append(indent)
+ data = b"".join(chunks)
+ return data
+
+
+# Mypy does not support recursive type aliases as of 0.782, Pylance does.
+# https://github.com/python/mypy/issues/731
+# https://devblogs.microsoft.com/python/pylance-introduces-five-new-features-that-enable-type-magic-for-python-developers/#1-support-for-recursive-type-aliases
+PlistEncodable = Union[
+ bool,
+ bytes,
+ Data,
+ datetime,
+ float,
+ int,
+ Mapping[str, Any],
+ Sequence[Any],
+ str,
+]
+
+
+class PlistTarget:
+ """Event handler using the ElementTree Target API that can be
+ passed to a XMLParser to produce property list objects from XML.
+ It is based on the CPython plistlib module's _PlistParser class,
+ but does not use the expat parser.
+
+ >>> from fontTools.misc import etree
+ >>> parser = etree.XMLParser(target=PlistTarget())
+ >>> result = etree.XML(
+ ... "<dict>"
+ ... " <key>something</key>"
+ ... " <string>blah</string>"
+ ... "</dict>",
+ ... parser=parser)
+ >>> result == {"something": "blah"}
+ True
+
+ Links:
+ https://github.com/python/cpython/blob/master/Lib/plistlib.py
+ http://lxml.de/parsing.html#the-target-parser-interface
+ """
+
+ def __init__(
+ self,
+ use_builtin_types: Optional[bool] = None,
+ dict_type: Type[MutableMapping[str, Any]] = dict,
+ ) -> None:
+ self.stack: List[PlistEncodable] = []
+ self.current_key: Optional[str] = None
+ self.root: Optional[PlistEncodable] = None
+ if use_builtin_types is None:
+ self._use_builtin_types = USE_BUILTIN_TYPES
+ else:
+ if use_builtin_types is False:
+ warnings.warn(
+ "Setting use_builtin_types to False is deprecated and will be "
+ "removed soon.",
+ DeprecationWarning,
+ )
+ self._use_builtin_types = use_builtin_types
+ self._dict_type = dict_type
+
+ def start(self, tag: str, attrib: Mapping[str, str]) -> None:
+ self._data: List[str] = []
+ handler = _TARGET_START_HANDLERS.get(tag)
+ if handler is not None:
+ handler(self)
+
+ def end(self, tag: str) -> None:
+ handler = _TARGET_END_HANDLERS.get(tag)
+ if handler is not None:
+ handler(self)
+
+ def data(self, data: str) -> None:
+ self._data.append(data)
+
+ def close(self) -> PlistEncodable:
+ if self.root is None:
+ raise ValueError("No root set.")
+ return self.root
+
+ # helpers
+
+ def add_object(self, value: PlistEncodable) -> None:
+ if self.current_key is not None:
+ stack_top = self.stack[-1]
+ if not isinstance(stack_top, collections.abc.MutableMapping):
+ raise ValueError("unexpected element: %r" % stack_top)
+ stack_top[self.current_key] = value
+ self.current_key = None
+ elif not self.stack:
+ # this is the root object
+ self.root = value
+ else:
+ stack_top = self.stack[-1]
+ if not isinstance(stack_top, list):
+ raise ValueError("unexpected element: %r" % stack_top)
+ stack_top.append(value)
+
+ def get_data(self) -> str:
+ data = "".join(self._data)
+ self._data = []
+ return data
+
+
+# event handlers
+
+
+def start_dict(self: PlistTarget) -> None:
+ d = self._dict_type()
+ self.add_object(d)
+ self.stack.append(d)
+
+
+def end_dict(self: PlistTarget) -> None:
+ if self.current_key:
+ raise ValueError("missing value for key '%s'" % self.current_key)
+ self.stack.pop()
+
+
+def end_key(self: PlistTarget) -> None:
+ if self.current_key or not isinstance(self.stack[-1], collections.abc.Mapping):
+ raise ValueError("unexpected key")
+ self.current_key = self.get_data()
+
+
+def start_array(self: PlistTarget) -> None:
+ a: List[PlistEncodable] = []
+ self.add_object(a)
+ self.stack.append(a)
+
+
+def end_array(self: PlistTarget) -> None:
+ self.stack.pop()
+
+
+def end_true(self: PlistTarget) -> None:
+ self.add_object(True)
+
+
+def end_false(self: PlistTarget) -> None:
+ self.add_object(False)
+
+
+def end_integer(self: PlistTarget) -> None:
+ self.add_object(int(self.get_data()))
+
+
+def end_real(self: PlistTarget) -> None:
+ self.add_object(float(self.get_data()))
+
+
+def end_string(self: PlistTarget) -> None:
+ self.add_object(self.get_data())
+
+
+def end_data(self: PlistTarget) -> None:
+ if self._use_builtin_types:
+ self.add_object(b64decode(self.get_data()))
+ else:
+ self.add_object(Data.fromBase64(self.get_data()))
+
+
+def end_date(self: PlistTarget) -> None:
+ self.add_object(_date_from_string(self.get_data()))
+
+
+_TARGET_START_HANDLERS: Dict[str, Callable[[PlistTarget], None]] = {
+ "dict": start_dict,
+ "array": start_array,
+}
+
+_TARGET_END_HANDLERS: Dict[str, Callable[[PlistTarget], None]] = {
+ "dict": end_dict,
+ "array": end_array,
+ "key": end_key,
+ "true": end_true,
+ "false": end_false,
+ "integer": end_integer,
+ "real": end_real,
+ "string": end_string,
+ "data": end_data,
+ "date": end_date,
+}
+
+
+# functions to build element tree from plist data
+
+
+def _string_element(value: str, ctx: SimpleNamespace) -> etree.Element:
+ el = etree.Element("string")
+ el.text = value
+ return el
+
+
+def _bool_element(value: bool, ctx: SimpleNamespace) -> etree.Element:
+ if value:
+ return etree.Element("true")
+ return etree.Element("false")
+
+
+def _integer_element(value: int, ctx: SimpleNamespace) -> etree.Element:
+ if -1 << 63 <= value < 1 << 64:
+ el = etree.Element("integer")
+ el.text = "%d" % value
+ return el
+ raise OverflowError(value)
+
+
+def _real_element(value: float, ctx: SimpleNamespace) -> etree.Element:
+ el = etree.Element("real")
+ el.text = repr(value)
+ return el
+
+
+def _dict_element(d: Mapping[str, PlistEncodable], ctx: SimpleNamespace) -> etree.Element:
+ el = etree.Element("dict")
+ items = d.items()
+ if ctx.sort_keys:
+ items = sorted(items) # type: ignore
+ ctx.indent_level += 1
+ for key, value in items:
+ if not isinstance(key, str):
+ if ctx.skipkeys:
+ continue
+ raise TypeError("keys must be strings")
+ k = etree.SubElement(el, "key")
+ k.text = tostr(key, "utf-8")
+ el.append(_make_element(value, ctx))
+ ctx.indent_level -= 1
+ return el
+
+
+def _array_element(array: Sequence[PlistEncodable], ctx: SimpleNamespace) -> etree.Element:
+ el = etree.Element("array")
+ if len(array) == 0:
+ return el
+ ctx.indent_level += 1
+ for value in array:
+ el.append(_make_element(value, ctx))
+ ctx.indent_level -= 1
+ return el
+
+
+def _date_element(date: datetime, ctx: SimpleNamespace) -> etree.Element:
+ el = etree.Element("date")
+ el.text = _date_to_string(date)
+ return el
+
+
+def _data_element(data: bytes, ctx: SimpleNamespace) -> etree.Element:
+ el = etree.Element("data")
+ # NOTE: mypy is confused about whether el.text should be str or bytes.
+ el.text = _encode_base64( # type: ignore
+ data,
+ maxlinelength=(76 if ctx.pretty_print else None),
+ indent_level=ctx.indent_level,
+ )
+ return el
+
+
+def _string_or_data_element(raw_bytes: bytes, ctx: SimpleNamespace) -> etree.Element:
+ if ctx.use_builtin_types:
+ return _data_element(raw_bytes, ctx)
+ else:
+ try:
+ string = raw_bytes.decode(encoding="ascii", errors="strict")
+ except UnicodeDecodeError:
+ raise ValueError(
+ "invalid non-ASCII bytes; use unicode string instead: %r" % raw_bytes
+ )
+ return _string_element(string, ctx)
+
+
+# The following is probably not entirely correct. The signature should take `Any`
+# and return `NoReturn`. At the time of this writing, neither mypy nor Pyright
+# can deal with singledispatch properly and will apply the signature of the base
+# function to all others. Being slightly dishonest makes it type-check and return
+# usable typing information for the optimistic case.
+@singledispatch
+def _make_element(value: PlistEncodable, ctx: SimpleNamespace) -> etree.Element:
+ raise TypeError("unsupported type: %s" % type(value))
+
+
+_make_element.register(str)(_string_element)
+_make_element.register(bool)(_bool_element)
+_make_element.register(Integral)(_integer_element)
+_make_element.register(float)(_real_element)
+_make_element.register(collections.abc.Mapping)(_dict_element)
+_make_element.register(list)(_array_element)
+_make_element.register(tuple)(_array_element)
+_make_element.register(datetime)(_date_element)
+_make_element.register(bytes)(_string_or_data_element)
+_make_element.register(bytearray)(_data_element)
+_make_element.register(Data)(lambda v, ctx: _data_element(v.data, ctx))
+
+
+# Public functions to create element tree from plist-compatible python
+# data structures and viceversa, for use when (de)serializing GLIF xml.
+
+
+def totree(
+ value: PlistEncodable,
+ sort_keys: bool = True,
+ skipkeys: bool = False,
+ use_builtin_types: Optional[bool] = None,
+ pretty_print: bool = True,
+ indent_level: int = 1,
+) -> etree.Element:
+ """Convert a value derived from a plist into an XML tree.
+
+ Args:
+ value: Any kind of value to be serialized to XML.
+ sort_keys: Whether keys of dictionaries should be sorted.
+ skipkeys (bool): Whether to silently skip non-string dictionary
+ keys.
+ use_builtin_types (bool): If true, byte strings will be
+ encoded in Base-64 and wrapped in a ``data`` tag; if
+ false, they will be either stored as ASCII strings or an
+ exception raised if they cannot be decoded as such. Defaults
+ to ``True`` if not present. Deprecated.
+ pretty_print (bool): Whether to indent the output.
+ indent_level (int): Level of indentation when serializing.
+
+ Returns: an ``etree`` ``Element`` object.
+
+ Raises:
+ ``TypeError``
+ if non-string dictionary keys are serialized
+ and ``skipkeys`` is false.
+ ``ValueError``
+ if non-ASCII binary data is present
+ and `use_builtin_types` is false.
+ """
+ if use_builtin_types is None:
+ use_builtin_types = USE_BUILTIN_TYPES
+ else:
+ use_builtin_types = use_builtin_types
+ context = SimpleNamespace(
+ sort_keys=sort_keys,
+ skipkeys=skipkeys,
+ use_builtin_types=use_builtin_types,
+ pretty_print=pretty_print,
+ indent_level=indent_level,
+ )
+ return _make_element(value, context)
+
+
+def fromtree(
+ tree: etree.Element,
+ use_builtin_types: Optional[bool] = None,
+ dict_type: Type[MutableMapping[str, Any]] = dict,
+) -> Any:
+ """Convert an XML tree to a plist structure.
+
+ Args:
+ tree: An ``etree`` ``Element``.
+ use_builtin_types: If True, binary data is deserialized to
+ bytes strings. If False, it is wrapped in :py:class:`Data`
+ objects. Defaults to True if not provided. Deprecated.
+ dict_type: What type to use for dictionaries.
+
+ Returns: An object (usually a dictionary).
+ """
+ target = PlistTarget(use_builtin_types=use_builtin_types, dict_type=dict_type)
+ for action, element in etree.iterwalk(tree, events=("start", "end")):
+ if action == "start":
+ target.start(element.tag, element.attrib)
+ elif action == "end":
+ # if there are no children, parse the leaf's data
+ if not len(element):
+ # always pass str, not None
+ target.data(element.text or "")
+ target.end(element.tag)
+ return target.close()
+
+
+# python3 plistlib API
+
+
+def load(
+ fp: IO[bytes],
+ use_builtin_types: Optional[bool] = None,
+ dict_type: Type[MutableMapping[str, Any]] = dict,
+) -> Any:
+ """Load a plist file into an object.
+
+ Args:
+ fp: An opened file.
+ use_builtin_types: If True, binary data is deserialized to
+ bytes strings. If False, it is wrapped in :py:class:`Data`
+ objects. Defaults to True if not provided. Deprecated.
+ dict_type: What type to use for dictionaries.
+
+ Returns:
+ An object (usually a dictionary) representing the top level of
+ the plist file.
+ """
+
+ if not hasattr(fp, "read"):
+ raise AttributeError("'%s' object has no attribute 'read'" % type(fp).__name__)
+ target = PlistTarget(use_builtin_types=use_builtin_types, dict_type=dict_type)
+ parser = etree.XMLParser(target=target)
+ result = etree.parse(fp, parser=parser)
+ # lxml returns the target object directly, while ElementTree wraps
+ # it as the root of an ElementTree object
+ try:
+ return result.getroot()
+ except AttributeError:
+ return result
+
+
+def loads(
+ value: bytes,
+ use_builtin_types: Optional[bool] = None,
+ dict_type: Type[MutableMapping[str, Any]] = dict,
+) -> Any:
+ """Load a plist file from a string into an object.
+
+ Args:
+ value: A bytes string containing a plist.
+ use_builtin_types: If True, binary data is deserialized to
+ bytes strings. If False, it is wrapped in :py:class:`Data`
+ objects. Defaults to True if not provided. Deprecated.
+ dict_type: What type to use for dictionaries.
+
+ Returns:
+ An object (usually a dictionary) representing the top level of
+ the plist file.
+ """
+
+ fp = BytesIO(value)
+ return load(fp, use_builtin_types=use_builtin_types, dict_type=dict_type)
+
+
+def dump(
+ value: PlistEncodable,
+ fp: IO[bytes],
+ sort_keys: bool = True,
+ skipkeys: bool = False,
+ use_builtin_types: Optional[bool] = None,
+ pretty_print: bool = True,
+) -> None:
+ """Write a Python object to a plist file.
+
+ Args:
+ value: An object to write.
+ fp: A file opened for writing.
+ sort_keys (bool): Whether keys of dictionaries should be sorted.
+ skipkeys (bool): Whether to silently skip non-string dictionary
+ keys.
+ use_builtin_types (bool): If true, byte strings will be
+ encoded in Base-64 and wrapped in a ``data`` tag; if
+ false, they will be either stored as ASCII strings or an
+ exception raised if they cannot be represented. Defaults
+ pretty_print (bool): Whether to indent the output.
+ indent_level (int): Level of indentation when serializing.
+
+ Raises:
+ ``TypeError``
+ if non-string dictionary keys are serialized
+ and ``skipkeys`` is false.
+ ``ValueError``
+ if non-representable binary data is present
+ and `use_builtin_types` is false.
+ """
+
+ if not hasattr(fp, "write"):
+ raise AttributeError("'%s' object has no attribute 'write'" % type(fp).__name__)
+ root = etree.Element("plist", version="1.0")
+ el = totree(
+ value,
+ sort_keys=sort_keys,
+ skipkeys=skipkeys,
+ use_builtin_types=use_builtin_types,
+ pretty_print=pretty_print,
+ )
+ root.append(el)
+ tree = etree.ElementTree(root)
+ # we write the doctype ourselves instead of using the 'doctype' argument
+ # of 'write' method, becuse lxml will force adding a '\n' even when
+ # pretty_print is False.
+ if pretty_print:
+ header = b"\n".join((XML_DECLARATION, PLIST_DOCTYPE, b""))
+ else:
+ header = XML_DECLARATION + PLIST_DOCTYPE
+ fp.write(header)
+ tree.write( # type: ignore
+ fp,
+ encoding="utf-8",
+ pretty_print=pretty_print,
+ xml_declaration=False,
+ )
+
+
+def dumps(
+ value: PlistEncodable,
+ sort_keys: bool = True,
+ skipkeys: bool = False,
+ use_builtin_types: Optional[bool] = None,
+ pretty_print: bool = True,
+) -> bytes:
+ """Write a Python object to a string in plist format.
+
+ Args:
+ value: An object to write.
+ sort_keys (bool): Whether keys of dictionaries should be sorted.
+ skipkeys (bool): Whether to silently skip non-string dictionary
+ keys.
+ use_builtin_types (bool): If true, byte strings will be
+ encoded in Base-64 and wrapped in a ``data`` tag; if
+ false, they will be either stored as strings or an
+ exception raised if they cannot be represented. Defaults
+ pretty_print (bool): Whether to indent the output.
+ indent_level (int): Level of indentation when serializing.
+
+ Returns:
+ string: A plist representation of the Python object.
+
+ Raises:
+ ``TypeError``
+ if non-string dictionary keys are serialized
+ and ``skipkeys`` is false.
+ ``ValueError``
+ if non-representable binary data is present
+ and `use_builtin_types` is false.
+ """
+ fp = BytesIO()
+ dump(
+ value,
+ fp,
+ sort_keys=sort_keys,
+ skipkeys=skipkeys,
+ use_builtin_types=use_builtin_types,
+ pretty_print=pretty_print,
+ )
+ return fp.getvalue()
diff --git a/Lib/fontTools/misc/plistlib/py.typed b/Lib/fontTools/misc/plistlib/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/Lib/fontTools/misc/plistlib/py.typed
diff --git a/Lib/fontTools/misc/psCharStrings.py b/Lib/fontTools/misc/psCharStrings.py
index a97ec96d..cb675050 100644
--- a/Lib/fontTools/misc/psCharStrings.py
+++ b/Lib/fontTools/misc/psCharStrings.py
@@ -2,9 +2,10 @@
CFF dictionary data and Type1/Type2 CharStrings.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import fixedToFloat, otRound
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, strjoin
+from fontTools.misc.fixedTools import (
+ fixedToFloat, floatToFixed, floatToFixedToStr, strToFixedToFloat,
+)
from fontTools.pens.boundsPen import BoundsPen
import struct
import logging
@@ -216,7 +217,7 @@ encodeIntT2 = getIntEncoder("t2")
def encodeFixed(f, pack=struct.pack):
"""For T2 only"""
- value = otRound(f * 65536) # convert the float to fixed point
+ value = floatToFixed(f, precisionBits=16)
if value & 0xFFFF == 0: # check if the fractional part is zero
return encodeIntT2(value >> 16) # encode only the integer part
else:
@@ -416,7 +417,6 @@ class SimpleT2Decompiler(object):
self.numRegions = self.private.getNumRegions()
numBlends = self.pop()
numOps = numBlends * (self.numRegions + 1)
- blendArgs = self.operandStack[-numOps:]
del self.operandStack[-(numOps-numBlends):] # Leave the default operands on the stack.
def op_vsindex(self, index):
@@ -997,7 +997,7 @@ class T2CharString(object):
# If present, remove return and endchar operators.
if program and program[-1] in ("return", "endchar"):
program = program[:-1]
- elif program and not isinstance(program[-1], basestring):
+ elif program and not isinstance(program[-1], str):
raise CharStringCompileError(
"T2CharString or Subr has items on the stack after last operator."
)
@@ -1010,7 +1010,7 @@ class T2CharString(object):
while i < end:
token = program[i]
i = i + 1
- if isinstance(token, basestring):
+ if isinstance(token, str):
try:
bytecode.extend(bytechr(b) for b in opcodes[token])
except KeyError:
@@ -1043,8 +1043,7 @@ class T2CharString(object):
self.program = None
def getToken(self, index,
- len=len, byteord=byteord, basestring=basestring,
- isinstance=isinstance):
+ len=len, byteord=byteord, isinstance=isinstance):
if self.bytecode is not None:
if index >= len(self.bytecode):
return None, 0, 0
@@ -1057,7 +1056,7 @@ class T2CharString(object):
return None, 0, 0
token = self.program[index]
index = index + 1
- isOperator = isinstance(token, basestring)
+ isOperator = isinstance(token, str)
return token, isOperator, index
def getBytes(self, index, nBytes):
@@ -1074,7 +1073,7 @@ class T2CharString(object):
def handle_operator(self, operator):
return operator
- def toXML(self, xmlWriter):
+ def toXML(self, xmlWriter, ttFont=None):
from fontTools.misc.textTools import num2binary
if self.bytecode is not None:
xmlWriter.dumphex(self.bytecode)
@@ -1086,7 +1085,6 @@ class T2CharString(object):
if token is None:
break
if isOperator:
- args = [str(arg) for arg in args]
if token in ('hintmask', 'cntrmask'):
hintMask, isOperator, index = self.getToken(index)
bits = []
@@ -1100,12 +1098,15 @@ class T2CharString(object):
xmlWriter.newline()
args = []
else:
+ if isinstance(token, float):
+ token = floatToFixedToStr(token, precisionBits=16)
+ else:
+ token = str(token)
args.append(token)
if args:
# NOTE: only CFF2 charstrings/subrs can have numeric arguments on
# the stack after the last operator. Compiling this would fail if
# this is part of CFF 1.0 table.
- args = [str(arg) for arg in args]
line = ' '.join(args)
xmlWriter.write(line)
@@ -1126,7 +1127,7 @@ class T2CharString(object):
token = int(token)
except ValueError:
try:
- token = float(token)
+ token = strToFixedToFloat(token, precisionBits=16)
except ValueError:
program.append(token)
if token in ('hintmask', 'cntrmask'):
@@ -1148,10 +1149,7 @@ class T1CharString(T2CharString):
operators, opcodes = buildOperatorDict(t1Operators)
def __init__(self, bytecode=None, program=None, subrs=None):
- if program is None:
- program = []
- self.bytecode = bytecode
- self.program = program
+ super().__init__(bytecode, program)
self.subrs = subrs
def getIntEncoder(self):
diff --git a/Lib/fontTools/misc/psLib.py b/Lib/fontTools/misc/psLib.py
index e069e7b6..916755ce 100644
--- a/Lib/fontTools/misc/psLib.py
+++ b/Lib/fontTools/misc/psLib.py
@@ -1,12 +1,23 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, tobytes, tostr
from fontTools.misc import eexec
-from .psOperators import *
+from .psOperators import (
+ PSOperators,
+ ps_StandardEncoding,
+ ps_array,
+ ps_boolean,
+ ps_dict,
+ ps_integer,
+ ps_literal,
+ ps_mark,
+ ps_name,
+ ps_operator,
+ ps_procedure,
+ ps_procmark,
+ ps_real,
+ ps_string,
+)
import re
-try:
- from collections.abc import Callable
-except ImportError: # python < 3.3
- from collections import Callable
+from collections.abc import Callable
from string import whitespace
import logging
diff --git a/Lib/fontTools/misc/psOperators.py b/Lib/fontTools/misc/psOperators.py
index 4aa0281b..3b378f59 100644
--- a/Lib/fontTools/misc/psOperators.py
+++ b/Lib/fontTools/misc/psOperators.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
_accessstrings = {0: "", 1: "readonly", 2: "executeonly", 3: "noaccess"}
diff --git a/Lib/fontTools/misc/py23.py b/Lib/fontTools/misc/py23.py
index 5cddfebc..9096e2ef 100644
--- a/Lib/fontTools/misc/py23.py
+++ b/Lib/fontTools/misc/py23.py
@@ -1,525 +1,148 @@
-"""Python 2/3 compat layer."""
+"""Python 2/3 compat layer leftovers."""
-from __future__ import print_function, division, absolute_import
-import sys
+import decimal as _decimal
+import math as _math
+import warnings
+from contextlib import redirect_stderr, redirect_stdout
+from io import BytesIO
+from io import StringIO as UnicodeIO
+from types import SimpleNamespace
+
+warnings.warn(
+ "The py23 module has been deprecated and will be removed in a future release. "
+ "Please update your code.",
+ DeprecationWarning,
+)
+
+__all__ = [
+ "basestring",
+ "bytechr",
+ "byteord",
+ "BytesIO",
+ "bytesjoin",
+ "open",
+ "Py23Error",
+ "range",
+ "RecursionError",
+ "round",
+ "SimpleNamespace",
+ "StringIO",
+ "strjoin",
+ "Tag",
+ "tobytes",
+ "tostr",
+ "tounicode",
+ "unichr",
+ "unicode",
+ "UnicodeIO",
+ "xrange",
+ "zip",
+]
-__all__ = ['basestring', 'unicode', 'unichr', 'byteord', 'bytechr', 'BytesIO',
- 'StringIO', 'UnicodeIO', 'strjoin', 'bytesjoin', 'tobytes', 'tostr',
- 'tounicode', 'Tag', 'open', 'range', 'xrange', 'round', 'Py23Error',
- 'SimpleNamespace', 'zip', 'RecursionError']
+class Py23Error(NotImplementedError):
+ pass
-class Py23Error(NotImplementedError):
- pass
-
-
-PY3 = sys.version_info[0] == 3
-PY2 = sys.version_info[0] == 2
-
-
-try:
- basestring = basestring
-except NameError:
- basestring = str
-
-try:
- unicode = unicode
-except NameError:
- unicode = str
-
-try:
- unichr = unichr
-
- if sys.maxunicode < 0x10FFFF:
- # workarounds for Python 2 "narrow" builds with UCS2-only support.
-
- _narrow_unichr = unichr
-
- def unichr(i):
- """
- Return the unicode character whose Unicode code is the integer 'i'.
- The valid range is 0 to 0x10FFFF inclusive.
-
- >>> _narrow_unichr(0xFFFF + 1)
- Traceback (most recent call last):
- File "<stdin>", line 1, in ?
- ValueError: unichr() arg not in range(0x10000) (narrow Python build)
- >>> unichr(0xFFFF + 1) == u'\U00010000'
- True
- >>> unichr(1114111) == u'\U0010FFFF'
- True
- >>> unichr(0x10FFFF + 1)
- Traceback (most recent call last):
- File "<stdin>", line 1, in ?
- ValueError: unichr() arg not in range(0x110000)
- """
- try:
- return _narrow_unichr(i)
- except ValueError:
- try:
- padded_hex_str = hex(i)[2:].zfill(8)
- escape_str = "\\U" + padded_hex_str
- return escape_str.decode("unicode-escape")
- except UnicodeDecodeError:
- raise ValueError('unichr() arg not in range(0x110000)')
-
- import re
- _unicode_escape_RE = re.compile(r'\\U[A-Fa-f0-9]{8}')
-
- def byteord(c):
- """
- Given a 8-bit or unicode character, return an integer representing the
- Unicode code point of the character. If a unicode argument is given, the
- character's code point must be in the range 0 to 0x10FFFF inclusive.
-
- >>> ord(u'\U00010000')
- Traceback (most recent call last):
- File "<stdin>", line 1, in ?
- TypeError: ord() expected a character, but string of length 2 found
- >>> byteord(u'\U00010000') == 0xFFFF + 1
- True
- >>> byteord(u'\U0010FFFF') == 1114111
- True
- """
- try:
- return ord(c)
- except TypeError as e:
- try:
- escape_str = c.encode('unicode-escape')
- if not _unicode_escape_RE.match(escape_str):
- raise
- hex_str = escape_str[3:]
- return int(hex_str, 16)
- except:
- raise TypeError(e)
-
- else:
- byteord = ord
- bytechr = chr
-
-except NameError:
- unichr = chr
- def bytechr(n):
- return bytes([n])
- def byteord(c):
- return c if isinstance(c, int) else ord(c)
-
-
-# the 'io' module provides the same I/O interface on both 2 and 3.
-# here we define an alias of io.StringIO to disambiguate it eternally...
-from io import BytesIO
-from io import StringIO as UnicodeIO
-try:
- # in python 2, by 'StringIO' we still mean a stream of *byte* strings
- from StringIO import StringIO
-except ImportError:
- # in Python 3, we mean instead a stream of *unicode* strings
- StringIO = UnicodeIO
-
-
-def strjoin(iterable, joiner=''):
- return tostr(joiner).join(iterable)
-
-def tobytes(s, encoding='ascii', errors='strict'):
- if not isinstance(s, bytes):
- return s.encode(encoding, errors)
- else:
- return s
-def tounicode(s, encoding='ascii', errors='strict'):
- if not isinstance(s, unicode):
- return s.decode(encoding, errors)
- else:
- return s
-
-if str == bytes:
- class Tag(str):
- def tobytes(self):
- if isinstance(self, bytes):
- return self
- else:
- return self.encode('latin1')
-
- tostr = tobytes
-
- bytesjoin = strjoin
-else:
- class Tag(str):
-
- @staticmethod
- def transcode(blob):
- if isinstance(blob, bytes):
- blob = blob.decode('latin-1')
- return blob
-
- def __new__(self, content):
- return str.__new__(self, self.transcode(content))
- def __ne__(self, other):
- return not self.__eq__(other)
- def __eq__(self, other):
- return str.__eq__(self, self.transcode(other))
-
- def __hash__(self):
- return str.__hash__(self)
-
- def tobytes(self):
- return self.encode('latin-1')
-
- tostr = tounicode
-
- def bytesjoin(iterable, joiner=b''):
- return tobytes(joiner).join(tobytes(item) for item in iterable)
-
-
-import os
-import io as _io
-
-try:
- from msvcrt import setmode as _setmode
-except ImportError:
- _setmode = None # only available on the Windows platform
-
-
-def open(file, mode='r', buffering=-1, encoding=None, errors=None,
- newline=None, closefd=True, opener=None):
- """ Wrapper around `io.open` that bridges the differences between Python 2
- and Python 3's built-in `open` functions. In Python 2, `io.open` is a
- backport of Python 3's `open`, whereas in Python 3, it is an alias of the
- built-in `open` function.
-
- One difference is that the 'opener' keyword argument is only supported in
- Python 3. Here we pass the value of 'opener' only when it is not None.
- This causes Python 2 to raise TypeError, complaining about the number of
- expected arguments, so it must be avoided in py2 or py2-3 contexts.
-
- Another difference between 2 and 3, this time on Windows, has to do with
- opening files by name or by file descriptor.
-
- On the Windows C runtime, the 'O_BINARY' flag is defined which disables
- the newlines translation ('\r\n' <=> '\n') when reading/writing files.
- On both Python 2 and 3 this flag is always set when opening files by name.
- This way, the newlines translation at the MSVCRT level doesn't interfere
- with the Python io module's own newlines translation.
-
- However, when opening files via fd, on Python 2 the fd is simply copied,
- regardless of whether it has the 'O_BINARY' flag set or not.
- This becomes a problem in the case of stdout, stdin, and stderr, because on
- Windows these are opened in text mode by default (ie. don't have the
- O_BINARY flag set).
-
- On Python 3, this issue has been fixed, and all fds are now opened in
- binary mode on Windows, including standard streams. Similarly here, I use
- the `_setmode` function to ensure that integer file descriptors are
- O_BINARY'ed before I pass them on to io.open.
-
- For more info, see: https://bugs.python.org/issue10841
- """
- if isinstance(file, int):
- # the 'file' argument is an integer file descriptor
- fd = file
- if fd < 0:
- raise ValueError('negative file descriptor')
- if _setmode:
- # `_setmode` function sets the line-end translation and returns the
- # value of the previous mode. AFAIK there's no `_getmode`, so to
- # check if the previous mode already had the bit set, I fist need
- # to duplicate the file descriptor, set the binary flag on the copy
- # and check the returned value.
- fdcopy = os.dup(fd)
- current_mode = _setmode(fdcopy, os.O_BINARY)
- if not (current_mode & os.O_BINARY):
- # the binary mode was not set: use the file descriptor's copy
- file = fdcopy
- if closefd:
- # close the original file descriptor
- os.close(fd)
- else:
- # ensure the copy is closed when the file object is closed
- closefd = True
- else:
- # original file descriptor already had binary flag, close copy
- os.close(fdcopy)
-
- if opener is not None:
- # "opener" is not supported on Python 2, use it at your own risk!
- return _io.open(
- file, mode, buffering, encoding, errors, newline, closefd,
- opener=opener)
- else:
- return _io.open(
- file, mode, buffering, encoding, errors, newline, closefd)
-
-
-# always use iterator for 'range' and 'zip' on both py 2 and 3
-try:
- range = xrange
-except NameError:
- range = range
+RecursionError = RecursionError
+StringIO = UnicodeIO
-def xrange(*args, **kwargs):
- raise Py23Error("'xrange' is not defined. Use 'range' instead.")
+basestring = str
+isclose = _math.isclose
+isfinite = _math.isfinite
+open = open
+range = range
+round = round3 = round
+unichr = chr
+unicode = str
+zip = zip
-try:
- from itertools import izip as zip
-except ImportError:
- zip = zip
+def bytechr(n):
+ return bytes([n])
-import math as _math
-try:
- isclose = _math.isclose
-except AttributeError:
- # math.isclose() was only added in Python 3.5
-
- _isinf = _math.isinf
- _fabs = _math.fabs
-
- def isclose(a, b, rel_tol=1e-09, abs_tol=0):
- """
- Python 2 implementation of Python 3.5 math.isclose()
- https://hg.python.org/cpython/file/v3.5.2/Modules/mathmodule.c#l1993
- """
- # sanity check on the inputs
- if rel_tol < 0 or abs_tol < 0:
- raise ValueError("tolerances must be non-negative")
- # short circuit exact equality -- needed to catch two infinities of
- # the same sign. And perhaps speeds things up a bit sometimes.
- if a == b:
- return True
- # This catches the case of two infinities of opposite sign, or
- # one infinity and one finite number. Two infinities of opposite
- # sign would otherwise have an infinite relative tolerance.
- # Two infinities of the same sign are caught by the equality check
- # above.
- if _isinf(a) or _isinf(b):
- return False
- # Cast to float to allow decimal.Decimal arguments
- if not isinstance(a, float):
- a = float(a)
- if not isinstance(b, float):
- b = float(b)
- # now do the regular computation
- # this is essentially the "weak" test from the Boost library
- diff = _fabs(b - a)
- result = ((diff <= _fabs(rel_tol * a)) or
- (diff <= _fabs(rel_tol * b)) or
- (diff <= abs_tol))
- return result
-
-
-try:
- _isfinite = _math.isfinite # Python >= 3.2
-except AttributeError:
- _isfinite = None
- _isnan = _math.isnan
- _isinf = _math.isinf
-
-
-def isfinite(f):
- """
- >>> isfinite(0.0)
- True
- >>> isfinite(-0.1)
- True
- >>> isfinite(1e10)
- True
- >>> isfinite(float("nan"))
- False
- >>> isfinite(float("+inf"))
- False
- >>> isfinite(float("-inf"))
- False
- """
- if _isfinite is not None:
- return _isfinite(f)
- else:
- return not (_isnan(f) or _isinf(f))
+def byteord(c):
+ return c if isinstance(c, int) else ord(c)
-import decimal as _decimal
+def strjoin(iterable, joiner=""):
+ return tostr(joiner).join(iterable)
+
+
+def tobytes(s, encoding="ascii", errors="strict"):
+ if not isinstance(s, bytes):
+ return s.encode(encoding, errors)
+ else:
+ return s
+
-if PY3:
- def round2(number, ndigits=None):
- """
- Implementation of Python 2 built-in round() function.
-
- Rounds a number to a given precision in decimal digits (default
- 0 digits). The result is a floating point number. Values are rounded
- to the closest multiple of 10 to the power minus ndigits; if two
- multiples are equally close, rounding is done away from 0.
-
- ndigits may be negative.
-
- See Python 2 documentation:
- https://docs.python.org/2/library/functions.html?highlight=round#round
- """
- if ndigits is None:
- ndigits = 0
-
- if ndigits < 0:
- exponent = 10 ** (-ndigits)
- quotient, remainder = divmod(number, exponent)
- if remainder >= exponent//2 and number >= 0:
- quotient += 1
- return float(quotient * exponent)
- else:
- exponent = _decimal.Decimal('10') ** (-ndigits)
-
- d = _decimal.Decimal.from_float(number).quantize(
- exponent, rounding=_decimal.ROUND_HALF_UP)
-
- return float(d)
-
- if sys.version_info[:2] >= (3, 6):
- # in Python 3.6, 'round3' is an alias to the built-in 'round'
- round = round3 = round
- else:
- # in Python3 < 3.6 we need work around the inconsistent behavior of
- # built-in round(), whereby floats accept a second None argument,
- # while integers raise TypeError. See https://bugs.python.org/issue27936
- _round = round
-
- def round3(number, ndigits=None):
- return _round(number) if ndigits is None else _round(number, ndigits)
-
- round = round3
-
-else:
- # in Python 2, 'round2' is an alias to the built-in 'round' and
- # 'round' is shadowed by 'round3'
- round2 = round
-
- def round3(number, ndigits=None):
- """
- Implementation of Python 3 built-in round() function.
-
- Rounds a number to a given precision in decimal digits (default
- 0 digits). This returns an int when ndigits is omitted or is None,
- otherwise the same type as the number.
-
- Values are rounded to the closest multiple of 10 to the power minus
- ndigits; if two multiples are equally close, rounding is done toward
- the even choice (aka "Banker's Rounding"). For example, both round(0.5)
- and round(-0.5) are 0, and round(1.5) is 2.
-
- ndigits may be negative.
-
- See Python 3 documentation:
- https://docs.python.org/3/library/functions.html?highlight=round#round
-
- Derived from python-future:
- https://github.com/PythonCharmers/python-future/blob/master/src/future/builtins/newround.py
- """
- if ndigits is None:
- ndigits = 0
- # return an int when called with one argument
- totype = int
- # shortcut if already an integer, or a float with no decimal digits
- inumber = totype(number)
- if inumber == number:
- return inumber
- else:
- # return the same type as the number, when called with two arguments
- totype = type(number)
-
- m = number * (10 ** ndigits)
- # if number is half-way between two multiples, and the mutliple that is
- # closer to zero is even, we use the (slow) pure-Python implementation
- if isclose(m % 1, .5) and int(m) % 2 == 0:
- if ndigits < 0:
- exponent = 10 ** (-ndigits)
- quotient, remainder = divmod(number, exponent)
- half = exponent//2
- if remainder > half or (remainder == half and quotient % 2 != 0):
- quotient += 1
- d = quotient * exponent
- else:
- exponent = _decimal.Decimal('10') ** (-ndigits) if ndigits != 0 else 1
-
- d = _decimal.Decimal.from_float(number).quantize(
- exponent, rounding=_decimal.ROUND_HALF_EVEN)
- else:
- # else we use the built-in round() as it produces the same results
- d = round2(number, ndigits)
-
- return totype(d)
-
- round = round3
-
-
-try:
- from types import SimpleNamespace
-except ImportError:
- class SimpleNamespace(object):
- """
- A backport of Python 3.3's ``types.SimpleNamespace``.
- """
- def __init__(self, **kwargs):
- self.__dict__.update(kwargs)
-
- def __repr__(self):
- keys = sorted(self.__dict__)
- items = ("{0}={1!r}".format(k, self.__dict__[k]) for k in keys)
- return "{0}({1})".format(type(self).__name__, ", ".join(items))
-
- def __eq__(self, other):
- return self.__dict__ == other.__dict__
-
-
-if sys.version_info[:2] > (3, 4):
- from contextlib import redirect_stdout, redirect_stderr
-else:
- # `redirect_stdout` was added with python3.4, while `redirect_stderr`
- # with python3.5. For simplicity, I redefine both for any versions
- # less than or equal to 3.4.
- # The code below is copied from:
- # https://github.com/python/cpython/blob/57161aa/Lib/contextlib.py
-
- class _RedirectStream(object):
-
- _stream = None
-
- def __init__(self, new_target):
- self._new_target = new_target
- # We use a list of old targets to make this CM re-entrant
- self._old_targets = []
-
- def __enter__(self):
- self._old_targets.append(getattr(sys, self._stream))
- setattr(sys, self._stream, self._new_target)
- return self._new_target
-
- def __exit__(self, exctype, excinst, exctb):
- setattr(sys, self._stream, self._old_targets.pop())
-
-
- class redirect_stdout(_RedirectStream):
- """Context manager for temporarily redirecting stdout to another file.
- # How to send help() to stderr
- with redirect_stdout(sys.stderr):
- help(dir)
- # How to write help() to a file
- with open('help.txt', 'w') as f:
- with redirect_stdout(f):
- help(pow)
- """
-
- _stream = "stdout"
-
-
- class redirect_stderr(_RedirectStream):
- """Context manager for temporarily redirecting stderr to another file."""
-
- _stream = "stderr"
-
-
-try:
- RecursionError = RecursionError
-except NameError:
- RecursionError = RuntimeError
-
-
-if __name__ == "__main__":
- import doctest, sys
- sys.exit(doctest.testmod().failed)
+def tounicode(s, encoding="ascii", errors="strict"):
+ if not isinstance(s, unicode):
+ return s.decode(encoding, errors)
+ else:
+ return s
+
+
+tostr = tounicode
+
+
+class Tag(str):
+ @staticmethod
+ def transcode(blob):
+ if isinstance(blob, bytes):
+ blob = blob.decode("latin-1")
+ return blob
+
+ def __new__(self, content):
+ return str.__new__(self, self.transcode(content))
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __eq__(self, other):
+ return str.__eq__(self, self.transcode(other))
+
+ def __hash__(self):
+ return str.__hash__(self)
+
+ def tobytes(self):
+ return self.encode("latin-1")
+
+
+def bytesjoin(iterable, joiner=b""):
+ return tobytes(joiner).join(tobytes(item) for item in iterable)
+
+
+def xrange(*args, **kwargs):
+ raise Py23Error("'xrange' is not defined. Use 'range' instead.")
+
+
+def round2(number, ndigits=None):
+ """
+ Implementation of Python 2 built-in round() function.
+ Rounds a number to a given precision in decimal digits (default
+ 0 digits). The result is a floating point number. Values are rounded
+ to the closest multiple of 10 to the power minus ndigits; if two
+ multiples are equally close, rounding is done away from 0.
+ ndigits may be negative.
+ See Python 2 documentation:
+ https://docs.python.org/2/library/functions.html?highlight=round#round
+ """
+ if ndigits is None:
+ ndigits = 0
+
+ if ndigits < 0:
+ exponent = 10 ** (-ndigits)
+ quotient, remainder = divmod(number, exponent)
+ if remainder >= exponent // 2 and number >= 0:
+ quotient += 1
+ return float(quotient * exponent)
+ else:
+ exponent = _decimal.Decimal("10") ** (-ndigits)
+
+ d = _decimal.Decimal.from_float(number).quantize(
+ exponent, rounding=_decimal.ROUND_HALF_UP
+ )
+
+ return float(d)
diff --git a/Lib/fontTools/misc/roundTools.py b/Lib/fontTools/misc/roundTools.py
new file mode 100644
index 00000000..c1d546f1
--- /dev/null
+++ b/Lib/fontTools/misc/roundTools.py
@@ -0,0 +1,58 @@
+"""
+Various round-to-integer helpers.
+"""
+
+import math
+import functools
+import logging
+
+log = logging.getLogger(__name__)
+
+__all__ = [
+ "noRound",
+ "otRound",
+ "maybeRound",
+ "roundFunc",
+]
+
+def noRound(value):
+ return value
+
+def otRound(value):
+ """Round float value to nearest integer towards ``+Infinity``.
+
+ The OpenType spec (in the section on `"normalization" of OpenType Font Variations <https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization>`_)
+ defines the required method for converting floating point values to
+ fixed-point. In particular it specifies the following rounding strategy:
+
+ for fractional values of 0.5 and higher, take the next higher integer;
+ for other fractional values, truncate.
+
+ This function rounds the floating-point value according to this strategy
+ in preparation for conversion to fixed-point.
+
+ Args:
+ value (float): The input floating-point value.
+
+ Returns
+ float: The rounded value.
+ """
+ # See this thread for how we ended up with this implementation:
+ # https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
+ return int(math.floor(value + 0.5))
+
+def maybeRound(v, tolerance, round=otRound):
+ rounded = round(v)
+ return rounded if abs(rounded - v) <= tolerance else v
+
+def roundFunc(tolerance, round=otRound):
+ if tolerance < 0:
+ raise ValueError("Rounding tolerance must be positive")
+
+ if tolerance == 0:
+ return noRound
+
+ if tolerance >= .5:
+ return round
+
+ return functools.partial(maybeRound, tolerance=tolerance, round=round)
diff --git a/Lib/fontTools/misc/sstruct.py b/Lib/fontTools/misc/sstruct.py
index 6b7e783e..ba1f8788 100644
--- a/Lib/fontTools/misc/sstruct.py
+++ b/Lib/fontTools/misc/sstruct.py
@@ -46,8 +46,7 @@ calcsize(fmt)
it returns the size of the data in bytes.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes, tostr
from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
import struct
import re
@@ -69,7 +68,7 @@ def pack(fmt, obj):
if name in fixes:
# fixed point conversion
value = fl2fi(value, fixes[name])
- elif isinstance(value, basestring):
+ elif isinstance(value, str):
value = tobytes(value)
elements.append(value)
data = struct.pack(*(formatstring,) + tuple(elements))
diff --git a/Lib/fontTools/misc/symfont.py b/Lib/fontTools/misc/symfont.py
index d09efac4..a1a87300 100644
--- a/Lib/fontTools/misc/symfont.py
+++ b/Lib/fontTools/misc/symfont.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
from functools import partial
from itertools import count
@@ -113,9 +111,7 @@ MomentXYPen = partial(GreenPen, func=x*y)
def printGreenPen(penName, funcs, file=sys.stdout):
print(
-'''from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.pens.basePen import BasePen
+'''from fontTools.pens.basePen import BasePen
class %s(BasePen):
diff --git a/Lib/fontTools/misc/testTools.py b/Lib/fontTools/misc/testTools.py
index 3759d4ed..1b258e37 100644
--- a/Lib/fontTools/misc/testTools.py
+++ b/Lib/fontTools/misc/testTools.py
@@ -1,17 +1,13 @@
"""Helpers for writing unit tests."""
-from __future__ import (print_function, division, absolute_import,
- unicode_literals)
-try:
- from collections.abc import Iterable
-except ImportError: # python < 3.3
- from collections import Iterable
+from collections.abc import Iterable
+from io import BytesIO
import os
import shutil
import sys
import tempfile
from unittest import TestCase as _TestCase
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes
from fontTools.misc.xmlWriter import XMLWriter
@@ -30,7 +26,7 @@ def parseXML(xmlSnippet):
xml = b"<root>"
if isinstance(xmlSnippet, bytes):
xml += xmlSnippet
- elif isinstance(xmlSnippet, unicode):
+ elif isinstance(xmlSnippet, str):
xml += tobytes(xmlSnippet, 'utf-8')
elif isinstance(xmlSnippet, Iterable):
xml += b"".join(tobytes(s, 'utf-8') for s in xmlSnippet)
@@ -73,6 +69,9 @@ class FakeFont:
def getReverseGlyphMap(self):
return self.reverseGlyphOrderDict_
+ def getGlyphNames(self):
+ return sorted(self.getGlyphOrder())
+
class TestXMLReader_(object):
def __init__(self):
diff --git a/Lib/fontTools/misc/textTools.py b/Lib/fontTools/misc/textTools.py
index 08c59909..072976af 100644
--- a/Lib/fontTools/misc/textTools.py
+++ b/Lib/fontTools/misc/textTools.py
@@ -1,8 +1,7 @@
"""fontTools.misc.textTools.py -- miscellaneous routines."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, strjoin, tobytes
import ast
import string
@@ -13,7 +12,7 @@ safeEval = ast.literal_eval
def readHex(content):
"""Convert a list of hex strings to binary data."""
- return deHexStr(strjoin(chunk for chunk in content if isinstance(chunk, basestring)))
+ return deHexStr(strjoin(chunk for chunk in content if isinstance(chunk, str)))
def deHexStr(hexdata):
diff --git a/Lib/fontTools/misc/timeTools.py b/Lib/fontTools/misc/timeTools.py
index 657d77f8..f4b84f6e 100644
--- a/Lib/fontTools/misc/timeTools.py
+++ b/Lib/fontTools/misc/timeTools.py
@@ -1,10 +1,9 @@
"""fontTools.misc.timeTools.py -- tools for working with OpenType timestamps.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import os
import time
+from datetime import datetime, timezone
import calendar
@@ -45,7 +44,12 @@ def timestampToString(value):
return asctime(time.gmtime(max(0, value + epoch_diff)))
def timestampFromString(value):
- return calendar.timegm(time.strptime(value)) - epoch_diff
+ wkday, mnth = value[:7].split()
+ t = datetime.strptime(value[7:], ' %d %H:%M:%S %Y')
+ t = t.replace(month=MONTHNAMES.index(mnth), tzinfo=timezone.utc)
+ wkday_idx = DAYNAMES.index(wkday)
+ assert t.weekday() == wkday_idx, '"' + value + '" has inconsistent weekday'
+ return int(t.timestamp()) - epoch_diff
def timestampNow():
# https://reproducible-builds.org/specs/source-date-epoch/
diff --git a/Lib/fontTools/misc/transform.py b/Lib/fontTools/misc/transform.py
index 1296571b..997598f5 100644
--- a/Lib/fontTools/misc/transform.py
+++ b/Lib/fontTools/misc/transform.py
@@ -45,8 +45,8 @@ Examples:
>>>
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from typing import NamedTuple
+
__all__ = ["Transform", "Identity", "Offset", "Scale"]
@@ -66,7 +66,7 @@ def _normSinCos(v):
return v
-class Transform(object):
+class Transform(NamedTuple):
"""2x2 transformation matrix plus offset, a.k.a. Affine transform.
Transform instances are immutable: all transforming methods, eg.
@@ -83,20 +83,68 @@ class Transform(object):
>>>
>>> t.scale(2, 3).transformPoint((100, 100))
(200, 300)
+
+ Transform's constructor takes six arguments, all of which are
+ optional, and can be used as keyword arguments:
+ >>> Transform(12)
+ <Transform [12 0 0 1 0 0]>
+ >>> Transform(dx=12)
+ <Transform [1 0 0 1 12 0]>
+ >>> Transform(yx=12)
+ <Transform [1 0 12 1 0 0]>
+
+ Transform instances also behave like sequences of length 6:
+ >>> len(Identity)
+ 6
+ >>> list(Identity)
+ [1, 0, 0, 1, 0, 0]
+ >>> tuple(Identity)
+ (1, 0, 0, 1, 0, 0)
+
+ Transform instances are comparable:
+ >>> t1 = Identity.scale(2, 3).translate(4, 6)
+ >>> t2 = Identity.translate(8, 18).scale(2, 3)
+ >>> t1 == t2
+ 1
+
+ But beware of floating point rounding errors:
+ >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
+ >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
+ >>> t1
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
+ >>> t2
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
+ >>> t1 == t2
+ 0
+
+ Transform instances are hashable, meaning you can use them as
+ keys in dictionaries:
+ >>> d = {Scale(12, 13): None}
+ >>> d
+ {<Transform [12 0 0 13 0 0]>: None}
+
+ But again, beware of floating point rounding errors:
+ >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
+ >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
+ >>> t1
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
+ >>> t2
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
+ >>> d = {t1: None}
+ >>> d
+ {<Transform [0.2 0 0 0.3 0.08 0.18]>: None}
+ >>> d[t2]
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in ?
+ KeyError: <Transform [0.2 0 0 0.3 0.08 0.18]>
"""
- def __init__(self, xx=1, xy=0, yx=0, yy=1, dx=0, dy=0):
- """Transform's constructor takes six arguments, all of which are
- optional, and can be used as keyword arguments:
- >>> Transform(12)
- <Transform [12 0 0 1 0 0]>
- >>> Transform(dx=12)
- <Transform [1 0 0 1 12 0]>
- >>> Transform(yx=12)
- <Transform [1 0 12 1 0 0]>
- >>>
- """
- self.__affine = xx, xy, yx, yy, dx, dy
+ xx: float = 1
+ xy: float = 0
+ yx: float = 0
+ yy: float = 1
+ dx: float = 0
+ dy: float = 0
def transformPoint(self, p):
"""Transform a point.
@@ -108,7 +156,7 @@ class Transform(object):
(250.0, 550.0)
"""
(x, y) = p
- xx, xy, yx, yy, dx, dy = self.__affine
+ xx, xy, yx, yy, dx, dy = self
return (xx*x + yx*y + dx, xy*x + yy*y + dy)
def transformPoints(self, points):
@@ -120,7 +168,7 @@ class Transform(object):
[(0, 0), (0, 300), (200, 300), (200, 0)]
>>>
"""
- xx, xy, yx, yy, dx, dy = self.__affine
+ xx, xy, yx, yy, dx, dy = self
return [(xx*x + yx*y + dx, xy*x + yy*y + dy) for x, y in points]
def translate(self, x=0, y=0):
@@ -189,7 +237,7 @@ class Transform(object):
>>>
"""
xx1, xy1, yx1, yy1, dx1, dy1 = other
- xx2, xy2, yx2, yy2, dx2, dy2 = self.__affine
+ xx2, xy2, yx2, yy2, dx2, dy2 = self
return self.__class__(
xx1*xx2 + xy1*yx2,
xx1*xy2 + xy1*yy2,
@@ -211,7 +259,7 @@ class Transform(object):
<Transform [8 6 6 3 21 15]>
>>>
"""
- xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine
+ xx1, xy1, yx1, yy1, dx1, dy1 = self
xx2, xy2, yx2, yy2, dx2, dy2 = other
return self.__class__(
xx1*xx2 + xy1*yx2,
@@ -233,9 +281,9 @@ class Transform(object):
(10.0, 20.0)
>>>
"""
- if self.__affine == (1, 0, 0, 1, 0, 0):
+ if self == Identity:
return self
- xx, xy, yx, yy, dx, dy = self.__affine
+ xx, xy, yx, yy, dx, dy = self
det = xx*yy - yx*xy
xx, xy, yx, yy = yy/det, -xy/det, -yx/det, xx/det
dx, dy = -xx*dx - yx*dy, -xy*dx - yy*dy
@@ -248,77 +296,7 @@ class Transform(object):
'[2 0 0 3 8 15]'
>>>
"""
- return "[%s %s %s %s %s %s]" % self.__affine
-
- def __len__(self):
- """Transform instances also behave like sequences of length 6:
- >>> len(Identity)
- 6
- >>>
- """
- return 6
-
- def __getitem__(self, index):
- """Transform instances also behave like sequences of length 6:
- >>> list(Identity)
- [1, 0, 0, 1, 0, 0]
- >>> tuple(Identity)
- (1, 0, 0, 1, 0, 0)
- >>>
- """
- return self.__affine[index]
-
- def __ne__(self, other):
- return not self.__eq__(other)
- def __eq__(self, other):
- """Transform instances are comparable:
- >>> t1 = Identity.scale(2, 3).translate(4, 6)
- >>> t2 = Identity.translate(8, 18).scale(2, 3)
- >>> t1 == t2
- 1
- >>>
-
- But beware of floating point rounding errors:
- >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
- >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
- >>> t1
- <Transform [0.2 0 0 0.3 0.08 0.18]>
- >>> t2
- <Transform [0.2 0 0 0.3 0.08 0.18]>
- >>> t1 == t2
- 0
- >>>
- """
- xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine
- xx2, xy2, yx2, yy2, dx2, dy2 = other
- return (xx1, xy1, yx1, yy1, dx1, dy1) == \
- (xx2, xy2, yx2, yy2, dx2, dy2)
-
- def __hash__(self):
- """Transform instances are hashable, meaning you can use them as
- keys in dictionaries:
- >>> d = {Scale(12, 13): None}
- >>> d
- {<Transform [12 0 0 13 0 0]>: None}
- >>>
-
- But again, beware of floating point rounding errors:
- >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
- >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
- >>> t1
- <Transform [0.2 0 0 0.3 0.08 0.18]>
- >>> t2
- <Transform [0.2 0 0 0.3 0.08 0.18]>
- >>> d = {t1: None}
- >>> d
- {<Transform [0.2 0 0 0.3 0.08 0.18]>: None}
- >>> d[t2]
- Traceback (most recent call last):
- File "<stdin>", line 1, in ?
- KeyError: <Transform [0.2 0 0 0.3 0.08 0.18]>
- >>>
- """
- return hash(self.__affine)
+ return "[%s %s %s %s %s %s]" % self
def __bool__(self):
"""Returns True if transform is not identity, False otherwise.
@@ -337,13 +315,10 @@ class Transform(object):
>>> bool(Offset(2))
True
"""
- return self.__affine != Identity.__affine
-
- __nonzero__ = __bool__
+ return self != Identity
def __repr__(self):
- return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) \
- + self.__affine)
+ return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) + self)
Identity = Transform()
diff --git a/Lib/fontTools/misc/vector.py b/Lib/fontTools/misc/vector.py
new file mode 100644
index 00000000..81c14841
--- /dev/null
+++ b/Lib/fontTools/misc/vector.py
@@ -0,0 +1,143 @@
+from numbers import Number
+import math
+import operator
+import warnings
+
+
+__all__ = ["Vector"]
+
+
+class Vector(tuple):
+
+ """A math-like vector.
+
+ Represents an n-dimensional numeric vector. ``Vector`` objects support
+ vector addition and subtraction, scalar multiplication and division,
+ negation, rounding, and comparison tests.
+ """
+
+ __slots__ = ()
+
+ def __new__(cls, values, keep=False):
+ if keep is not False:
+ warnings.warn(
+ "the 'keep' argument has been deprecated",
+ DeprecationWarning,
+ )
+ if type(values) == Vector:
+ # No need to create a new object
+ return values
+ return super().__new__(cls, values)
+
+ def __repr__(self):
+ return f"{self.__class__.__name__}({super().__repr__()})"
+
+ def _vectorOp(self, other, op):
+ if isinstance(other, Vector):
+ assert len(self) == len(other)
+ return self.__class__(op(a, b) for a, b in zip(self, other))
+ if isinstance(other, Number):
+ return self.__class__(op(v, other) for v in self)
+ raise NotImplementedError()
+
+ def _scalarOp(self, other, op):
+ if isinstance(other, Number):
+ return self.__class__(op(v, other) for v in self)
+ raise NotImplementedError()
+
+ def _unaryOp(self, op):
+ return self.__class__(op(v) for v in self)
+
+ def __add__(self, other):
+ return self._vectorOp(other, operator.add)
+
+ __radd__ = __add__
+
+ def __sub__(self, other):
+ return self._vectorOp(other, operator.sub)
+
+ def __rsub__(self, other):
+ return self._vectorOp(other, _operator_rsub)
+
+ def __mul__(self, other):
+ return self._scalarOp(other, operator.mul)
+
+ __rmul__ = __mul__
+
+ def __truediv__(self, other):
+ return self._scalarOp(other, operator.truediv)
+
+ def __rtruediv__(self, other):
+ return self._scalarOp(other, _operator_rtruediv)
+
+ def __pos__(self):
+ return self._unaryOp(operator.pos)
+
+ def __neg__(self):
+ return self._unaryOp(operator.neg)
+
+ def __round__(self, *, round=round):
+ return self._unaryOp(round)
+
+ def __eq__(self, other):
+ if isinstance(other, list):
+ # bw compat Vector([1, 2, 3]) == [1, 2, 3]
+ other = tuple(other)
+ return super().__eq__(other)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __bool__(self):
+ return any(self)
+
+ __nonzero__ = __bool__
+
+ def __abs__(self):
+ return math.sqrt(sum(x * x for x in self))
+
+ def length(self):
+ """Return the length of the vector. Equivalent to abs(vector)."""
+ return abs(self)
+
+ def normalized(self):
+ """Return the normalized vector of the vector."""
+ return self / abs(self)
+
+ def dot(self, other):
+ """Performs vector dot product, returning the sum of
+ ``a[0] * b[0], a[1] * b[1], ...``"""
+ assert len(self) == len(other)
+ return sum(a * b for a, b in zip(self, other))
+
+ # Deprecated methods/properties
+
+ def toInt(self):
+ warnings.warn(
+ "the 'toInt' method has been deprecated, use round(vector) instead",
+ DeprecationWarning,
+ )
+ return self.__round__()
+
+ @property
+ def values(self):
+ warnings.warn(
+ "the 'values' attribute has been deprecated, use "
+ "the vector object itself instead",
+ DeprecationWarning,
+ )
+ return list(self)
+
+ @values.setter
+ def values(self, values):
+ raise AttributeError(
+ "can't set attribute, the 'values' attribute has been deprecated",
+ )
+
+
+def _operator_rsub(a, b):
+ return operator.sub(b, a)
+
+
+def _operator_rtruediv(a, b):
+ return operator.truediv(b, a)
diff --git a/Lib/fontTools/misc/xmlReader.py b/Lib/fontTools/misc/xmlReader.py
index e9b5cfd8..b2707e99 100644
--- a/Lib/fontTools/misc/xmlReader.py
+++ b/Lib/fontTools/misc/xmlReader.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
from fontTools.ttLib.tables.DefaultTable import DefaultTable
diff --git a/Lib/fontTools/misc/xmlWriter.py b/Lib/fontTools/misc/xmlWriter.py
index ef51a79a..fec127a9 100644
--- a/Lib/fontTools/misc/xmlWriter.py
+++ b/Lib/fontTools/misc/xmlWriter.py
@@ -1,7 +1,6 @@
"""xmlWriter.py -- Simple XML authoring class"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import byteord, strjoin, tobytes, tostr
import sys
import os
import string
@@ -35,8 +34,8 @@ class XMLWriter(object):
self.totype = tobytes
except TypeError:
# This better not fail.
- self.file.write(tounicode(''))
- self.totype = tounicode
+ self.file.write('')
+ self.totype = tostr
self.indentwhite = self.totype(indentwhite)
if newlinestr is None:
self.newlinestr = self.totype(os.linesep)
@@ -157,7 +156,7 @@ class XMLWriter(object):
return ""
data = ""
for attr, value in attributes:
- if not isinstance(value, (bytes, unicode)):
+ if not isinstance(value, (bytes, str)):
value = str(value)
data = data + ' %s="%s"' % (attr, escapeattr(value))
return data
diff --git a/Lib/fontTools/mtiLib/__init__.py b/Lib/fontTools/mtiLib/__init__.py
index beec5cb5..667a216d 100644
--- a/Lib/fontTools/mtiLib/__init__.py
+++ b/Lib/fontTools/mtiLib/__init__.py
@@ -6,9 +6,6 @@
# http://monotype.github.io/OpenType_Table_Source/otl_source.html
# https://github.com/Monotype/OpenType_Table_Source/
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
from fontTools import ttLib
from fontTools.ttLib.tables._c_m_a_p import cmap_classes
from fontTools.ttLib.tables import otTables as ot
@@ -856,7 +853,7 @@ def parseLookup(lines, tableTag, font, lookupMap=None):
lookup.SubTable = subtables
lookup.SubTableCount = len(lookup.SubTable)
- if lookup.SubTableCount is 0:
+ if lookup.SubTableCount == 0:
# Remove this return when following is fixed:
# https://github.com/fonttools/fonttools/issues/789
return None
@@ -1148,11 +1145,33 @@ class Tokenizer(object):
return line
def build(f, font, tableTag=None):
+ """Convert a Monotype font layout file to an OpenType layout object
+
+ A font object must be passed, but this may be a "dummy" font; it is only
+ used for sorting glyph sets when making coverage tables and to hold the
+ OpenType layout table while it is being built.
+
+ Args:
+ f: A file object.
+ font (TTFont): A font object.
+ tableTag (string): If provided, asserts that the file contains data for the
+ given OpenType table.
+
+ Returns:
+ An object representing the table. (e.g. ``table_G_S_U_B_``)
+ """
lines = Tokenizer(f)
return parseTable(lines, font, tableTag=tableTag)
def main(args=None, font=None):
+ """Convert a FontDame OTL file to TTX XML.
+
+ Writes XML output to stdout.
+
+ Args:
+ args: Command line arguments (``--font``, ``--table``, input files).
+ """
import sys
from fontTools import configLogger
from fontTools.misc.testTools import MockFont
@@ -1165,17 +1184,31 @@ def main(args=None, font=None):
# comment this out to enable debug messages from mtiLib's logger
# log.setLevel(logging.DEBUG)
+ import argparse
+ parser = argparse.ArgumentParser(
+ "fonttools mtiLib",
+ description=main.__doc__,
+ )
+
+ parser.add_argument('--font', '-f', metavar='FILE', dest="font",
+ help="Input TTF files (used for glyph classes and sorting coverage tables)")
+ parser.add_argument('--table', '-t', metavar='TABLE', dest="tableTag",
+ help="Table to fill (sniffed from input file if not provided)")
+ parser.add_argument('inputs', metavar='FILE', type=str, nargs='+',
+ help="Input FontDame .txt files")
+
+ args = parser.parse_args(args)
+
if font is None:
- font = MockFont()
+ if args.font:
+ font = ttLib.TTFont(args.font)
+ else:
+ font = MockFont()
- tableTag = None
- if args[0].startswith('-t'):
- tableTag = args[0][2:]
- del args[0]
- for f in args:
+ for f in args.inputs:
log.debug("Processing %s", f)
with open(f, 'rt', encoding="utf-8") as f:
- table = build(f, font, tableTag=tableTag)
+ table = build(f, font, tableTag=args.tableTag)
blob = table.compile(font) # Make sure it compiles
decompiled = table.__class__()
decompiled.decompile(blob, font) # Make sure it decompiles!
diff --git a/Lib/fontTools/mtiLib/__main__.py b/Lib/fontTools/mtiLib/__main__.py
index 0741237f..fe6b638b 100644
--- a/Lib/fontTools/mtiLib/__main__.py
+++ b/Lib/fontTools/mtiLib/__main__.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import sys
from fontTools.mtiLib import main
diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py
index a087b2d3..182f7da6 100644
--- a/Lib/fontTools/otlLib/builder.py
+++ b/Lib/fontTools/otlLib/builder.py
@@ -1,11 +1,52 @@
-from __future__ import print_function, division, absolute_import
-from collections import namedtuple
+from collections import namedtuple, OrderedDict
+from fontTools.misc.fixedTools import fixedToFloat
from fontTools import ttLib
from fontTools.ttLib.tables import otTables as ot
-from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
+from fontTools.ttLib.tables.otBase import (
+ ValueRecord,
+ valueRecordFormatDict,
+ OTTableWriter,
+ CountReference,
+)
+from fontTools.ttLib.tables import otBase
+from fontTools.feaLib.ast import STATNameStatement
+from fontTools.otlLib.error import OpenTypeLibError
+from functools import reduce
+import logging
+import copy
+
+
+log = logging.getLogger(__name__)
def buildCoverage(glyphs, glyphMap):
+ """Builds a coverage table.
+
+ Coverage tables (as defined in the `OpenType spec <https://docs.microsoft.com/en-gb/typography/opentype/spec/chapter2#coverage-table>`_)
+ are used in all OpenType Layout lookups apart from the Extension type, and
+ define the glyphs involved in a layout subtable. This allows shaping engines
+ to compare the glyph stream with the coverage table and quickly determine
+ whether a subtable should be involved in a shaping operation.
+
+ This function takes a list of glyphs and a glyphname-to-ID map, and
+ returns a ``Coverage`` object representing the coverage table.
+
+ Example::
+
+ glyphMap = font.getReverseGlyphMap()
+ glyphs = [ "A", "B", "C" ]
+ coverage = buildCoverage(glyphs, glyphMap)
+
+ Args:
+ glyphs: a sequence of glyph names.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ An ``otTables.Coverage`` object or ``None`` if there are no glyphs
+ supplied.
+ """
+
if not glyphs:
return None
self = ot.Coverage()
@@ -21,36 +62,1399 @@ LOOKUP_FLAG_USE_MARK_FILTERING_SET = 0x0010
def buildLookup(subtables, flags=0, markFilterSet=None):
+ """Turns a collection of rules into a lookup.
+
+ A Lookup (as defined in the `OpenType Spec <https://docs.microsoft.com/en-gb/typography/opentype/spec/chapter2#lookupTbl>`_)
+ wraps the individual rules in a layout operation (substitution or
+ positioning) in a data structure expressing their overall lookup type -
+ for example, single substitution, mark-to-base attachment, and so on -
+ as well as the lookup flags and any mark filtering sets. You may import
+ the following constants to express lookup flags:
+
+ - ``LOOKUP_FLAG_RIGHT_TO_LEFT``
+ - ``LOOKUP_FLAG_IGNORE_BASE_GLYPHS``
+ - ``LOOKUP_FLAG_IGNORE_LIGATURES``
+ - ``LOOKUP_FLAG_IGNORE_MARKS``
+ - ``LOOKUP_FLAG_USE_MARK_FILTERING_SET``
+
+ Args:
+ subtables: A list of layout subtable objects (e.g.
+ ``MultipleSubst``, ``PairPos``, etc.) or ``None``.
+ flags (int): This lookup's flags.
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+
+ Returns:
+ An ``otTables.Lookup`` object or ``None`` if there are no subtables
+ supplied.
+ """
if subtables is None:
return None
subtables = [st for st in subtables if st is not None]
if not subtables:
return None
- assert all(t.LookupType == subtables[0].LookupType for t in subtables), \
- ("all subtables must have the same LookupType; got %s" %
- repr([t.LookupType for t in subtables]))
+ assert all(
+ t.LookupType == subtables[0].LookupType for t in subtables
+ ), "all subtables must have the same LookupType; got %s" % repr(
+ [t.LookupType for t in subtables]
+ )
self = ot.Lookup()
self.LookupType = subtables[0].LookupType
self.LookupFlag = flags
self.SubTable = subtables
self.SubTableCount = len(self.SubTable)
if markFilterSet is not None:
- assert self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET, \
- ("if markFilterSet is not None, flags must set "
- "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags)
+ self.LookupFlag |= LOOKUP_FLAG_USE_MARK_FILTERING_SET
assert isinstance(markFilterSet, int), markFilterSet
self.MarkFilteringSet = markFilterSet
else:
- assert (self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET) == 0, \
- ("if markFilterSet is None, flags must not set "
- "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags)
+ assert (self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET) == 0, (
+ "if markFilterSet is None, flags must not set "
+ "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags
+ )
return self
+class LookupBuilder(object):
+ SUBTABLE_BREAK_ = "SUBTABLE_BREAK"
+
+ def __init__(self, font, location, table, lookup_type):
+ self.font = font
+ self.glyphMap = font.getReverseGlyphMap()
+ self.location = location
+ self.table, self.lookup_type = table, lookup_type
+ self.lookupflag = 0
+ self.markFilterSet = None
+ self.lookup_index = None # assigned when making final tables
+ assert table in ("GPOS", "GSUB")
+
+ def equals(self, other):
+ return (
+ isinstance(other, self.__class__)
+ and self.table == other.table
+ and self.lookupflag == other.lookupflag
+ and self.markFilterSet == other.markFilterSet
+ )
+
+ def inferGlyphClasses(self):
+ """Infers glyph glasses for the GDEF table, such as {"cedilla":3}."""
+ return {}
+
+ def getAlternateGlyphs(self):
+ """Helper for building 'aalt' features."""
+ return {}
+
+ def buildLookup_(self, subtables):
+ return buildLookup(subtables, self.lookupflag, self.markFilterSet)
+
+ def buildMarkClasses_(self, marks):
+ """{"cedilla": ("BOTTOM", ast.Anchor), ...} --> {"BOTTOM":0, "TOP":1}
+
+ Helper for MarkBasePostBuilder, MarkLigPosBuilder, and
+ MarkMarkPosBuilder. Seems to return the same numeric IDs
+ for mark classes as the AFDKO makeotf tool.
+ """
+ ids = {}
+ for mark in sorted(marks.keys(), key=self.font.getGlyphID):
+ markClassName, _markAnchor = marks[mark]
+ if markClassName not in ids:
+ ids[markClassName] = len(ids)
+ return ids
+
+ def setBacktrackCoverage_(self, prefix, subtable):
+ subtable.BacktrackGlyphCount = len(prefix)
+ subtable.BacktrackCoverage = []
+ for p in reversed(prefix):
+ coverage = buildCoverage(p, self.glyphMap)
+ subtable.BacktrackCoverage.append(coverage)
+
+ def setLookAheadCoverage_(self, suffix, subtable):
+ subtable.LookAheadGlyphCount = len(suffix)
+ subtable.LookAheadCoverage = []
+ for s in suffix:
+ coverage = buildCoverage(s, self.glyphMap)
+ subtable.LookAheadCoverage.append(coverage)
+
+ def setInputCoverage_(self, glyphs, subtable):
+ subtable.InputGlyphCount = len(glyphs)
+ subtable.InputCoverage = []
+ for g in glyphs:
+ coverage = buildCoverage(g, self.glyphMap)
+ subtable.InputCoverage.append(coverage)
+
+ def setCoverage_(self, glyphs, subtable):
+ subtable.GlyphCount = len(glyphs)
+ subtable.Coverage = []
+ for g in glyphs:
+ coverage = buildCoverage(g, self.glyphMap)
+ subtable.Coverage.append(coverage)
+
+ def build_subst_subtables(self, mapping, klass):
+ substitutions = [{}]
+ for key in mapping:
+ if key[0] == self.SUBTABLE_BREAK_:
+ substitutions.append({})
+ else:
+ substitutions[-1][key] = mapping[key]
+ subtables = [klass(s) for s in substitutions]
+ return subtables
+
+ def add_subtable_break(self, location):
+ """Add an explicit subtable break.
+
+ Args:
+ location: A string or tuple representing the location in the
+ original source which produced this break, or ``None`` if
+ no location is provided.
+ """
+ log.warning(
+ OpenTypeLibError(
+ 'unsupported "subtable" statement for lookup type', location
+ )
+ )
+
+
+class AlternateSubstBuilder(LookupBuilder):
+ """Builds an Alternate Substitution (GSUB3) lookup.
+
+ Users are expected to manually add alternate glyph substitutions to
+ the ``alternates`` attribute after the object has been initialized,
+ e.g.::
+
+ builder.alternates["A"] = ["A.alt1", "A.alt2"]
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ alternates: An ordered dictionary of alternates, mapping glyph names
+ to a list of names of alternates.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GSUB", 3)
+ self.alternates = OrderedDict()
+
+ def equals(self, other):
+ return LookupBuilder.equals(self, other) and self.alternates == other.alternates
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the alternate
+ substitution lookup.
+ """
+ subtables = self.build_subst_subtables(
+ self.alternates, buildAlternateSubstSubtable
+ )
+ return self.buildLookup_(subtables)
+
+ def getAlternateGlyphs(self):
+ return self.alternates
+
+ def add_subtable_break(self, location):
+ self.alternates[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
+
+
+class ChainContextualRule(
+ namedtuple("ChainContextualRule", ["prefix", "glyphs", "suffix", "lookups"])
+):
+ @property
+ def is_subtable_break(self):
+ return self.prefix == LookupBuilder.SUBTABLE_BREAK_
+
+
+class ChainContextualRuleset:
+ def __init__(self):
+ self.rules = []
+
+ def addRule(self, rule):
+ self.rules.append(rule)
+
+ @property
+ def hasPrefixOrSuffix(self):
+ # Do we have any prefixes/suffixes? If this is False for all
+ # rulesets, we can express the whole lookup as GPOS5/GSUB7.
+ for rule in self.rules:
+ if len(rule.prefix) > 0 or len(rule.suffix) > 0:
+ return True
+ return False
+
+ @property
+ def hasAnyGlyphClasses(self):
+ # Do we use glyph classes anywhere in the rules? If this is False
+ # we can express this subtable as a Format 1.
+ for rule in self.rules:
+ for coverage in (rule.prefix, rule.glyphs, rule.suffix):
+ if any(len(x) > 1 for x in coverage):
+ return True
+ return False
+
+ def format2ClassDefs(self):
+ PREFIX, GLYPHS, SUFFIX = 0, 1, 2
+ classDefBuilders = []
+ for ix in [PREFIX, GLYPHS, SUFFIX]:
+ context = []
+ for r in self.rules:
+ context.append(r[ix])
+ classes = self._classBuilderForContext(context)
+ if not classes:
+ return None
+ classDefBuilders.append(classes)
+ return classDefBuilders
+
+ def _classBuilderForContext(self, context):
+ classdefbuilder = ClassDefBuilder(useClass0=False)
+ for position in context:
+ for glyphset in position:
+ glyphs = set(glyphset)
+ if not classdefbuilder.canAdd(glyphs):
+ return None
+ classdefbuilder.add(glyphs)
+ return classdefbuilder
+
+
+class ChainContextualBuilder(LookupBuilder):
+ def equals(self, other):
+ return LookupBuilder.equals(self, other) and self.rules == other.rules
+
+ def rulesets(self):
+ # Return a list of ChainContextRuleset objects, taking explicit
+ # subtable breaks into account
+ ruleset = [ChainContextualRuleset()]
+ for rule in self.rules:
+ if rule.is_subtable_break:
+ ruleset.append(ChainContextualRuleset())
+ continue
+ ruleset[-1].addRule(rule)
+ # Squish any empty subtables
+ return [x for x in ruleset if len(x.rules) > 0]
+
+ def getCompiledSize_(self, subtables):
+ size = 0
+ for st in subtables:
+ w = OTTableWriter()
+ w["LookupType"] = CountReference(
+ {"LookupType": st.LookupType}, "LookupType"
+ )
+ # We need to make a copy here because compiling
+ # modifies the subtable (finalizing formats etc.)
+ copy.deepcopy(st).compile(w, self.font)
+ size += len(w.getAllData())
+ return size
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the chained
+ contextual positioning lookup.
+ """
+ subtables = []
+ chaining = False
+ rulesets = self.rulesets()
+ chaining = any(ruleset.hasPrefixOrSuffix for ruleset in rulesets)
+ for ruleset in rulesets:
+ # Determine format strategy. We try to build formats 1, 2 and 3
+ # subtables and then work out which is best. candidates list holds
+ # the subtables in each format for this ruleset (including a dummy
+ # "format 0" to make the addressing match the format numbers).
+
+ # We can always build a format 3 lookup by accumulating each of
+ # the rules into a list, so start with that.
+ candidates = [None, None, None, []]
+ for rule in ruleset.rules:
+ candidates[3].append(self.buildFormat3Subtable(rule, chaining))
+
+ # Can we express the whole ruleset as a format 2 subtable?
+ classdefs = ruleset.format2ClassDefs()
+ if classdefs:
+ candidates[2] = [
+ self.buildFormat2Subtable(ruleset, classdefs, chaining)
+ ]
+
+ if not ruleset.hasAnyGlyphClasses:
+ candidates[1] = [self.buildFormat1Subtable(ruleset, chaining)]
+
+ candidates = [x for x in candidates if x is not None]
+ winner = min(candidates, key=self.getCompiledSize_)
+ subtables.extend(winner)
+
+ # If we are not chaining, lookup type will be automatically fixed by
+ # buildLookup_
+ return self.buildLookup_(subtables)
+
+ def buildFormat1Subtable(self, ruleset, chaining=True):
+ st = self.newSubtable_(chaining=chaining)
+ st.Format = 1
+ st.populateDefaults()
+ coverage = set()
+ rulesetsByFirstGlyph = {}
+ ruleAttr = self.ruleAttr_(format=1, chaining=chaining)
+
+ for rule in ruleset.rules:
+ ruleAsSubtable = self.newRule_(format=1, chaining=chaining)
+
+ if chaining:
+ ruleAsSubtable.BacktrackGlyphCount = len(rule.prefix)
+ ruleAsSubtable.LookAheadGlyphCount = len(rule.suffix)
+ ruleAsSubtable.Backtrack = [list(x)[0] for x in reversed(rule.prefix)]
+ ruleAsSubtable.LookAhead = [list(x)[0] for x in rule.suffix]
+
+ ruleAsSubtable.InputGlyphCount = len(rule.glyphs)
+ else:
+ ruleAsSubtable.GlyphCount = len(rule.glyphs)
+
+ ruleAsSubtable.Input = [list(x)[0] for x in rule.glyphs[1:]]
+
+ self.buildLookupList(rule, ruleAsSubtable)
+
+ firstGlyph = list(rule.glyphs[0])[0]
+ if firstGlyph not in rulesetsByFirstGlyph:
+ coverage.add(firstGlyph)
+ rulesetsByFirstGlyph[firstGlyph] = []
+ rulesetsByFirstGlyph[firstGlyph].append(ruleAsSubtable)
+
+ st.Coverage = buildCoverage(coverage, self.glyphMap)
+ ruleSets = []
+ for g in st.Coverage.glyphs:
+ ruleSet = self.newRuleSet_(format=1, chaining=chaining)
+ setattr(ruleSet, ruleAttr, rulesetsByFirstGlyph[g])
+ setattr(ruleSet, f"{ruleAttr}Count", len(rulesetsByFirstGlyph[g]))
+ ruleSets.append(ruleSet)
+
+ setattr(st, self.ruleSetAttr_(format=1, chaining=chaining), ruleSets)
+ setattr(
+ st, self.ruleSetAttr_(format=1, chaining=chaining) + "Count", len(ruleSets)
+ )
+
+ return st
+
+ def buildFormat2Subtable(self, ruleset, classdefs, chaining=True):
+ st = self.newSubtable_(chaining=chaining)
+ st.Format = 2
+ st.populateDefaults()
+
+ if chaining:
+ (
+ st.BacktrackClassDef,
+ st.InputClassDef,
+ st.LookAheadClassDef,
+ ) = [c.build() for c in classdefs]
+ else:
+ st.ClassDef = classdefs[1].build()
+
+ inClasses = classdefs[1].classes()
+
+ classSets = []
+ for _ in inClasses:
+ classSet = self.newRuleSet_(format=2, chaining=chaining)
+ classSets.append(classSet)
+
+ coverage = set()
+ classRuleAttr = self.ruleAttr_(format=2, chaining=chaining)
+
+ for rule in ruleset.rules:
+ ruleAsSubtable = self.newRule_(format=2, chaining=chaining)
+ if chaining:
+ ruleAsSubtable.BacktrackGlyphCount = len(rule.prefix)
+ ruleAsSubtable.LookAheadGlyphCount = len(rule.suffix)
+ # The glyphs in the rule may be list, tuple, odict_keys...
+ # Order is not important anyway because they are guaranteed
+ # to be members of the same class.
+ ruleAsSubtable.Backtrack = [
+ st.BacktrackClassDef.classDefs[list(x)[0]]
+ for x in reversed(rule.prefix)
+ ]
+ ruleAsSubtable.LookAhead = [
+ st.LookAheadClassDef.classDefs[list(x)[0]] for x in rule.suffix
+ ]
+
+ ruleAsSubtable.InputGlyphCount = len(rule.glyphs)
+ ruleAsSubtable.Input = [
+ st.InputClassDef.classDefs[list(x)[0]] for x in rule.glyphs[1:]
+ ]
+ setForThisRule = classSets[
+ st.InputClassDef.classDefs[list(rule.glyphs[0])[0]]
+ ]
+ else:
+ ruleAsSubtable.GlyphCount = len(rule.glyphs)
+ ruleAsSubtable.Class = [ # The spec calls this InputSequence
+ st.ClassDef.classDefs[list(x)[0]] for x in rule.glyphs[1:]
+ ]
+ setForThisRule = classSets[
+ st.ClassDef.classDefs[list(rule.glyphs[0])[0]]
+ ]
+
+ self.buildLookupList(rule, ruleAsSubtable)
+ coverage |= set(rule.glyphs[0])
+
+ getattr(setForThisRule, classRuleAttr).append(ruleAsSubtable)
+ setattr(
+ setForThisRule,
+ f"{classRuleAttr}Count",
+ getattr(setForThisRule, f"{classRuleAttr}Count") + 1,
+ )
+ setattr(st, self.ruleSetAttr_(format=2, chaining=chaining), classSets)
+ setattr(
+ st, self.ruleSetAttr_(format=2, chaining=chaining) + "Count", len(classSets)
+ )
+ st.Coverage = buildCoverage(coverage, self.glyphMap)
+ return st
+
+ def buildFormat3Subtable(self, rule, chaining=True):
+ st = self.newSubtable_(chaining=chaining)
+ st.Format = 3
+ if chaining:
+ self.setBacktrackCoverage_(rule.prefix, st)
+ self.setLookAheadCoverage_(rule.suffix, st)
+ self.setInputCoverage_(rule.glyphs, st)
+ else:
+ self.setCoverage_(rule.glyphs, st)
+ self.buildLookupList(rule, st)
+ return st
+
+ def buildLookupList(self, rule, st):
+ for sequenceIndex, lookupList in enumerate(rule.lookups):
+ if lookupList is not None:
+ if not isinstance(lookupList, list):
+ # Can happen with synthesised lookups
+ lookupList = [lookupList]
+ for l in lookupList:
+ if l.lookup_index is None:
+ if isinstance(self, ChainContextPosBuilder):
+ other = "substitution"
+ else:
+ other = "positioning"
+ raise OpenTypeLibError(
+ "Missing index of the specified "
+ f"lookup, might be a {other} lookup",
+ self.location,
+ )
+ rec = self.newLookupRecord_(st)
+ rec.SequenceIndex = sequenceIndex
+ rec.LookupListIndex = l.lookup_index
+
+ def add_subtable_break(self, location):
+ self.rules.append(
+ ChainContextualRule(
+ self.SUBTABLE_BREAK_,
+ self.SUBTABLE_BREAK_,
+ self.SUBTABLE_BREAK_,
+ [self.SUBTABLE_BREAK_],
+ )
+ )
+
+ def newSubtable_(self, chaining=True):
+ subtablename = f"Context{self.subtable_type}"
+ if chaining:
+ subtablename = "Chain" + subtablename
+ st = getattr(ot, subtablename)() # ot.ChainContextPos()/ot.ChainSubst()/etc.
+ setattr(st, f"{self.subtable_type}Count", 0)
+ setattr(st, f"{self.subtable_type}LookupRecord", [])
+ return st
+
+ # Format 1 and format 2 GSUB5/GSUB6/GPOS7/GPOS8 rulesets and rules form a family:
+ #
+ # format 1 ruleset format 1 rule format 2 ruleset format 2 rule
+ # GSUB5 SubRuleSet SubRule SubClassSet SubClassRule
+ # GSUB6 ChainSubRuleSet ChainSubRule ChainSubClassSet ChainSubClassRule
+ # GPOS7 PosRuleSet PosRule PosClassSet PosClassRule
+ # GPOS8 ChainPosRuleSet ChainPosRule ChainPosClassSet ChainPosClassRule
+ #
+ # The following functions generate the attribute names and subtables according
+ # to this naming convention.
+ def ruleSetAttr_(self, format=1, chaining=True):
+ if format == 1:
+ formatType = "Rule"
+ elif format == 2:
+ formatType = "Class"
+ else:
+ raise AssertionError(formatType)
+ subtablename = f"{self.subtable_type[0:3]}{formatType}Set" # Sub, not Subst.
+ if chaining:
+ subtablename = "Chain" + subtablename
+ return subtablename
+
+ def ruleAttr_(self, format=1, chaining=True):
+ if format == 1:
+ formatType = ""
+ elif format == 2:
+ formatType = "Class"
+ else:
+ raise AssertionError(formatType)
+ subtablename = f"{self.subtable_type[0:3]}{formatType}Rule" # Sub, not Subst.
+ if chaining:
+ subtablename = "Chain" + subtablename
+ return subtablename
+
+ def newRuleSet_(self, format=1, chaining=True):
+ st = getattr(
+ ot, self.ruleSetAttr_(format, chaining)
+ )() # ot.ChainPosRuleSet()/ot.SubRuleSet()/etc.
+ st.populateDefaults()
+ return st
+
+ def newRule_(self, format=1, chaining=True):
+ st = getattr(
+ ot, self.ruleAttr_(format, chaining)
+ )() # ot.ChainPosClassRule()/ot.SubClassRule()/etc.
+ st.populateDefaults()
+ return st
+
+ def attachSubtableWithCount_(
+ self, st, subtable_name, count_name, existing=None, index=None, chaining=False
+ ):
+ if chaining:
+ subtable_name = "Chain" + subtable_name
+ count_name = "Chain" + count_name
+
+ if not hasattr(st, count_name):
+ setattr(st, count_name, 0)
+ setattr(st, subtable_name, [])
+
+ if existing:
+ new_subtable = existing
+ else:
+ # Create a new, empty subtable from otTables
+ new_subtable = getattr(ot, subtable_name)()
+
+ setattr(st, count_name, getattr(st, count_name) + 1)
+
+ if index:
+ getattr(st, subtable_name).insert(index, new_subtable)
+ else:
+ getattr(st, subtable_name).append(new_subtable)
+
+ return new_subtable
+
+ def newLookupRecord_(self, st):
+ return self.attachSubtableWithCount_(
+ st,
+ f"{self.subtable_type}LookupRecord",
+ f"{self.subtable_type}Count",
+ chaining=False,
+ ) # Oddly, it isn't ChainSubstLookupRecord
+
+
+class ChainContextPosBuilder(ChainContextualBuilder):
+ """Builds a Chained Contextual Positioning (GPOS8) lookup.
+
+ Users are expected to manually add rules to the ``rules`` attribute after
+ the object has been initialized, e.g.::
+
+ # pos [A B] [C D] x' lookup lu1 y' z' lookup lu2 E;
+
+ prefix = [ ["A", "B"], ["C", "D"] ]
+ suffix = [ ["E"] ]
+ glyphs = [ ["x"], ["y"], ["z"] ]
+ lookups = [ [lu1], None, [lu2] ]
+ builder.rules.append( (prefix, glyphs, suffix, lookups) )
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ rules: A list of tuples representing the rules in this lookup.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GPOS", 8)
+ self.rules = []
+ self.subtable_type = "Pos"
+
+ def find_chainable_single_pos(self, lookups, glyphs, value):
+ """Helper for add_single_pos_chained_()"""
+ res = None
+ for lookup in lookups[::-1]:
+ if lookup == self.SUBTABLE_BREAK_:
+ return res
+ if isinstance(lookup, SinglePosBuilder) and all(
+ lookup.can_add(glyph, value) for glyph in glyphs
+ ):
+ res = lookup
+ return res
+
+
+class ChainContextSubstBuilder(ChainContextualBuilder):
+ """Builds a Chained Contextual Substitution (GSUB6) lookup.
+
+ Users are expected to manually add rules to the ``rules`` attribute after
+ the object has been initialized, e.g.::
+
+ # sub [A B] [C D] x' lookup lu1 y' z' lookup lu2 E;
+
+ prefix = [ ["A", "B"], ["C", "D"] ]
+ suffix = [ ["E"] ]
+ glyphs = [ ["x"], ["y"], ["z"] ]
+ lookups = [ [lu1], None, [lu2] ]
+ builder.rules.append( (prefix, glyphs, suffix, lookups) )
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ rules: A list of tuples representing the rules in this lookup.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GSUB", 6)
+ self.rules = [] # (prefix, input, suffix, lookups)
+ self.subtable_type = "Subst"
+
+ def getAlternateGlyphs(self):
+ result = {}
+ for rule in self.rules:
+ if rule.is_subtable_break:
+ continue
+ for lookups in rule.lookups:
+ if not isinstance(lookups, list):
+ lookups = [lookups]
+ for lookup in lookups:
+ if lookup is not None:
+ alts = lookup.getAlternateGlyphs()
+ for glyph, replacements in alts.items():
+ result.setdefault(glyph, set()).update(replacements)
+ return result
+
+ def find_chainable_single_subst(self, glyphs):
+ """Helper for add_single_subst_chained_()"""
+ res = None
+ for rule in self.rules[::-1]:
+ if rule.is_subtable_break:
+ return res
+ for sub in rule.lookups:
+ if isinstance(sub, SingleSubstBuilder) and not any(
+ g in glyphs for g in sub.mapping.keys()
+ ):
+ res = sub
+ return res
+
+
+class LigatureSubstBuilder(LookupBuilder):
+ """Builds a Ligature Substitution (GSUB4) lookup.
+
+ Users are expected to manually add ligatures to the ``ligatures``
+ attribute after the object has been initialized, e.g.::
+
+ # sub f i by f_i;
+ builder.ligatures[("f","f","i")] = "f_f_i"
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ ligatures: An ordered dictionary mapping a tuple of glyph names to the
+ ligature glyphname.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GSUB", 4)
+ self.ligatures = OrderedDict() # {('f','f','i'): 'f_f_i'}
+
+ def equals(self, other):
+ return LookupBuilder.equals(self, other) and self.ligatures == other.ligatures
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the ligature
+ substitution lookup.
+ """
+ subtables = self.build_subst_subtables(
+ self.ligatures, buildLigatureSubstSubtable
+ )
+ return self.buildLookup_(subtables)
+
+ def add_subtable_break(self, location):
+ self.ligatures[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
+
+
+class MultipleSubstBuilder(LookupBuilder):
+ """Builds a Multiple Substitution (GSUB2) lookup.
+
+ Users are expected to manually add substitutions to the ``mapping``
+ attribute after the object has been initialized, e.g.::
+
+ # sub uni06C0 by uni06D5.fina hamza.above;
+ builder.mapping["uni06C0"] = [ "uni06D5.fina", "hamza.above"]
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ mapping: An ordered dictionary mapping a glyph name to a list of
+ substituted glyph names.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GSUB", 2)
+ self.mapping = OrderedDict()
+
+ def equals(self, other):
+ return LookupBuilder.equals(self, other) and self.mapping == other.mapping
+
+ def build(self):
+ subtables = self.build_subst_subtables(self.mapping, buildMultipleSubstSubtable)
+ return self.buildLookup_(subtables)
+
+ def add_subtable_break(self, location):
+ self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
+
+
+class CursivePosBuilder(LookupBuilder):
+ """Builds a Cursive Positioning (GPOS3) lookup.
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ attachments: An ordered dictionary mapping a glyph name to a two-element
+ tuple of ``otTables.Anchor`` objects.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GPOS", 3)
+ self.attachments = {}
+
+ def equals(self, other):
+ return (
+ LookupBuilder.equals(self, other) and self.attachments == other.attachments
+ )
+
+ def add_attachment(self, location, glyphs, entryAnchor, exitAnchor):
+ """Adds attachment information to the cursive positioning lookup.
+
+ Args:
+ location: A string or tuple representing the location in the
+ original source which produced this lookup. (Unused.)
+ glyphs: A list of glyph names sharing these entry and exit
+ anchor locations.
+ entryAnchor: A ``otTables.Anchor`` object representing the
+ entry anchor, or ``None`` if no entry anchor is present.
+ exitAnchor: A ``otTables.Anchor`` object representing the
+ exit anchor, or ``None`` if no exit anchor is present.
+ """
+ for glyph in glyphs:
+ self.attachments[glyph] = (entryAnchor, exitAnchor)
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the cursive
+ positioning lookup.
+ """
+ st = buildCursivePosSubtable(self.attachments, self.glyphMap)
+ return self.buildLookup_([st])
+
+
+class MarkBasePosBuilder(LookupBuilder):
+ """Builds a Mark-To-Base Positioning (GPOS4) lookup.
+
+ Users are expected to manually add marks and bases to the ``marks``
+ and ``bases`` attributes after the object has been initialized, e.g.::
+
+ builder.marks["acute"] = (0, a1)
+ builder.marks["grave"] = (0, a1)
+ builder.marks["cedilla"] = (1, a2)
+ builder.bases["a"] = {0: a3, 1: a5}
+ builder.bases["b"] = {0: a4, 1: a5}
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ marks: An dictionary mapping a glyph name to a two-element
+ tuple containing a mark class ID and ``otTables.Anchor`` object.
+ bases: An dictionary mapping a glyph name to a dictionary of
+ mark class IDs and ``otTables.Anchor`` object.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GPOS", 4)
+ self.marks = {} # glyphName -> (markClassName, anchor)
+ self.bases = {} # glyphName -> {markClassName: anchor}
+
+ def equals(self, other):
+ return (
+ LookupBuilder.equals(self, other)
+ and self.marks == other.marks
+ and self.bases == other.bases
+ )
+
+ def inferGlyphClasses(self):
+ result = {glyph: 1 for glyph in self.bases}
+ result.update({glyph: 3 for glyph in self.marks})
+ return result
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the mark-to-base
+ positioning lookup.
+ """
+ markClasses = self.buildMarkClasses_(self.marks)
+ marks = {
+ mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items()
+ }
+ bases = {}
+ for glyph, anchors in self.bases.items():
+ bases[glyph] = {markClasses[mc]: anchor for (mc, anchor) in anchors.items()}
+ subtables = buildMarkBasePos(marks, bases, self.glyphMap)
+ return self.buildLookup_(subtables)
+
+
+class MarkLigPosBuilder(LookupBuilder):
+ """Builds a Mark-To-Ligature Positioning (GPOS5) lookup.
+
+ Users are expected to manually add marks and bases to the ``marks``
+ and ``ligatures`` attributes after the object has been initialized, e.g.::
+
+ builder.marks["acute"] = (0, a1)
+ builder.marks["grave"] = (0, a1)
+ builder.marks["cedilla"] = (1, a2)
+ builder.ligatures["f_i"] = [
+ { 0: a3, 1: a5 }, # f
+ { 0: a4, 1: a5 } # i
+ ]
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ marks: An dictionary mapping a glyph name to a two-element
+ tuple containing a mark class ID and ``otTables.Anchor`` object.
+ ligatures: An dictionary mapping a glyph name to an array with one
+ element for each ligature component. Each array element should be
+ a dictionary mapping mark class IDs to ``otTables.Anchor`` objects.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GPOS", 5)
+ self.marks = {} # glyphName -> (markClassName, anchor)
+ self.ligatures = {} # glyphName -> [{markClassName: anchor}, ...]
+
+ def equals(self, other):
+ return (
+ LookupBuilder.equals(self, other)
+ and self.marks == other.marks
+ and self.ligatures == other.ligatures
+ )
+
+ def inferGlyphClasses(self):
+ result = {glyph: 2 for glyph in self.ligatures}
+ result.update({glyph: 3 for glyph in self.marks})
+ return result
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the mark-to-ligature
+ positioning lookup.
+ """
+ markClasses = self.buildMarkClasses_(self.marks)
+ marks = {
+ mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items()
+ }
+ ligs = {}
+ for lig, components in self.ligatures.items():
+ ligs[lig] = []
+ for c in components:
+ ligs[lig].append({markClasses[mc]: a for mc, a in c.items()})
+ subtables = buildMarkLigPos(marks, ligs, self.glyphMap)
+ return self.buildLookup_(subtables)
+
+
+class MarkMarkPosBuilder(LookupBuilder):
+ """Builds a Mark-To-Mark Positioning (GPOS6) lookup.
+
+ Users are expected to manually add marks and bases to the ``marks``
+ and ``baseMarks`` attributes after the object has been initialized, e.g.::
+
+ builder.marks["acute"] = (0, a1)
+ builder.marks["grave"] = (0, a1)
+ builder.marks["cedilla"] = (1, a2)
+ builder.baseMarks["acute"] = {0: a3}
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ marks: An dictionary mapping a glyph name to a two-element
+ tuple containing a mark class ID and ``otTables.Anchor`` object.
+ baseMarks: An dictionary mapping a glyph name to a dictionary
+ containing one item: a mark class ID and a ``otTables.Anchor`` object.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GPOS", 6)
+ self.marks = {} # glyphName -> (markClassName, anchor)
+ self.baseMarks = {} # glyphName -> {markClassName: anchor}
+
+ def equals(self, other):
+ return (
+ LookupBuilder.equals(self, other)
+ and self.marks == other.marks
+ and self.baseMarks == other.baseMarks
+ )
+
+ def inferGlyphClasses(self):
+ result = {glyph: 3 for glyph in self.baseMarks}
+ result.update({glyph: 3 for glyph in self.marks})
+ return result
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the mark-to-mark
+ positioning lookup.
+ """
+ markClasses = self.buildMarkClasses_(self.marks)
+ markClassList = sorted(markClasses.keys(), key=markClasses.get)
+ marks = {
+ mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items()
+ }
+
+ st = ot.MarkMarkPos()
+ st.Format = 1
+ st.ClassCount = len(markClasses)
+ st.Mark1Coverage = buildCoverage(marks, self.glyphMap)
+ st.Mark2Coverage = buildCoverage(self.baseMarks, self.glyphMap)
+ st.Mark1Array = buildMarkArray(marks, self.glyphMap)
+ st.Mark2Array = ot.Mark2Array()
+ st.Mark2Array.Mark2Count = len(st.Mark2Coverage.glyphs)
+ st.Mark2Array.Mark2Record = []
+ for base in st.Mark2Coverage.glyphs:
+ anchors = [self.baseMarks[base].get(mc) for mc in markClassList]
+ st.Mark2Array.Mark2Record.append(buildMark2Record(anchors))
+ return self.buildLookup_([st])
+
+
+class ReverseChainSingleSubstBuilder(LookupBuilder):
+ """Builds a Reverse Chaining Contextual Single Substitution (GSUB8) lookup.
+
+ Users are expected to manually add substitutions to the ``substitutions``
+ attribute after the object has been initialized, e.g.::
+
+ # reversesub [a e n] d' by d.alt;
+ prefix = [ ["a", "e", "n"] ]
+ suffix = []
+ mapping = { "d": "d.alt" }
+ builder.substitutions.append( (prefix, suffix, mapping) )
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ substitutions: A three-element tuple consisting of a prefix sequence,
+ a suffix sequence, and a dictionary of single substitutions.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GSUB", 8)
+ self.rules = [] # (prefix, suffix, mapping)
+
+ def equals(self, other):
+ return LookupBuilder.equals(self, other) and self.rules == other.rules
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the chained
+ contextual substitution lookup.
+ """
+ subtables = []
+ for prefix, suffix, mapping in self.rules:
+ st = ot.ReverseChainSingleSubst()
+ st.Format = 1
+ self.setBacktrackCoverage_(prefix, st)
+ self.setLookAheadCoverage_(suffix, st)
+ st.Coverage = buildCoverage(mapping.keys(), self.glyphMap)
+ st.GlyphCount = len(mapping)
+ st.Substitute = [mapping[g] for g in st.Coverage.glyphs]
+ subtables.append(st)
+ return self.buildLookup_(subtables)
+
+ def add_subtable_break(self, location):
+ # Nothing to do here, each substitution is in its own subtable.
+ pass
+
+
+class SingleSubstBuilder(LookupBuilder):
+ """Builds a Single Substitution (GSUB1) lookup.
+
+ Users are expected to manually add substitutions to the ``mapping``
+ attribute after the object has been initialized, e.g.::
+
+ # sub x by y;
+ builder.mapping["x"] = "y"
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ mapping: A dictionary mapping a single glyph name to another glyph name.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GSUB", 1)
+ self.mapping = OrderedDict()
+
+ def equals(self, other):
+ return LookupBuilder.equals(self, other) and self.mapping == other.mapping
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the multiple
+ substitution lookup.
+ """
+ subtables = self.build_subst_subtables(self.mapping, buildSingleSubstSubtable)
+ return self.buildLookup_(subtables)
+
+ def getAlternateGlyphs(self):
+ return {glyph: set([repl]) for glyph, repl in self.mapping.items()}
+
+ def add_subtable_break(self, location):
+ self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
+
+
+class ClassPairPosSubtableBuilder(object):
+ """Builds class-based Pair Positioning (GPOS2 format 2) subtables.
+
+ Note that this does *not* build a GPOS2 ``otTables.Lookup`` directly,
+ but builds a list of ``otTables.PairPos`` subtables. It is used by the
+ :class:`PairPosBuilder` below.
+
+ Attributes:
+ builder (PairPosBuilder): A pair positioning lookup builder.
+ """
+
+ def __init__(self, builder):
+ self.builder_ = builder
+ self.classDef1_, self.classDef2_ = None, None
+ self.values_ = {} # (glyphclass1, glyphclass2) --> (value1, value2)
+ self.forceSubtableBreak_ = False
+ self.subtables_ = []
+
+ def addPair(self, gc1, value1, gc2, value2):
+ """Add a pair positioning rule.
+
+ Args:
+ gc1: A set of glyph names for the "left" glyph
+ value1: An ``otTables.ValueRecord`` object for the left glyph's
+ positioning.
+ gc2: A set of glyph names for the "right" glyph
+ value2: An ``otTables.ValueRecord`` object for the right glyph's
+ positioning.
+ """
+ mergeable = (
+ not self.forceSubtableBreak_
+ and self.classDef1_ is not None
+ and self.classDef1_.canAdd(gc1)
+ and self.classDef2_ is not None
+ and self.classDef2_.canAdd(gc2)
+ )
+ if not mergeable:
+ self.flush_()
+ self.classDef1_ = ClassDefBuilder(useClass0=True)
+ self.classDef2_ = ClassDefBuilder(useClass0=False)
+ self.values_ = {}
+ self.classDef1_.add(gc1)
+ self.classDef2_.add(gc2)
+ self.values_[(gc1, gc2)] = (value1, value2)
+
+ def addSubtableBreak(self):
+ """Add an explicit subtable break at this point."""
+ self.forceSubtableBreak_ = True
+
+ def subtables(self):
+ """Return the list of ``otTables.PairPos`` subtables constructed."""
+ self.flush_()
+ return self.subtables_
+
+ def flush_(self):
+ if self.classDef1_ is None or self.classDef2_ is None:
+ return
+ st = buildPairPosClassesSubtable(self.values_, self.builder_.glyphMap)
+ if st.Coverage is None:
+ return
+ self.subtables_.append(st)
+ self.forceSubtableBreak_ = False
+
+
+class PairPosBuilder(LookupBuilder):
+ """Builds a Pair Positioning (GPOS2) lookup.
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ pairs: An array of class-based pair positioning tuples. Usually
+ manipulated with the :meth:`addClassPair` method below.
+ glyphPairs: A dictionary mapping a tuple of glyph names to a tuple
+ of ``otTables.ValueRecord`` objects. Usually manipulated with the
+ :meth:`addGlyphPair` method below.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GPOS", 2)
+ self.pairs = [] # [(gc1, value1, gc2, value2)*]
+ self.glyphPairs = {} # (glyph1, glyph2) --> (value1, value2)
+ self.locations = {} # (gc1, gc2) --> (filepath, line, column)
+
+ def addClassPair(self, location, glyphclass1, value1, glyphclass2, value2):
+ """Add a class pair positioning rule to the current lookup.
+
+ Args:
+ location: A string or tuple representing the location in the
+ original source which produced this rule. Unused.
+ glyphclass1: A set of glyph names for the "left" glyph in the pair.
+ value1: A ``otTables.ValueRecord`` for positioning the left glyph.
+ glyphclass2: A set of glyph names for the "right" glyph in the pair.
+ value2: A ``otTables.ValueRecord`` for positioning the right glyph.
+ """
+ self.pairs.append((glyphclass1, value1, glyphclass2, value2))
+
+ def addGlyphPair(self, location, glyph1, value1, glyph2, value2):
+ """Add a glyph pair positioning rule to the current lookup.
+
+ Args:
+ location: A string or tuple representing the location in the
+ original source which produced this rule.
+ glyph1: A glyph name for the "left" glyph in the pair.
+ value1: A ``otTables.ValueRecord`` for positioning the left glyph.
+ glyph2: A glyph name for the "right" glyph in the pair.
+ value2: A ``otTables.ValueRecord`` for positioning the right glyph.
+ """
+ key = (glyph1, glyph2)
+ oldValue = self.glyphPairs.get(key, None)
+ if oldValue is not None:
+ # the Feature File spec explicitly allows specific pairs generated
+ # by an 'enum' rule to be overridden by preceding single pairs
+ otherLoc = self.locations[key]
+ log.debug(
+ "Already defined position for pair %s %s at %s; "
+ "choosing the first value",
+ glyph1,
+ glyph2,
+ otherLoc,
+ )
+ else:
+ self.glyphPairs[key] = (value1, value2)
+ self.locations[key] = location
+
+ def add_subtable_break(self, location):
+ self.pairs.append(
+ (
+ self.SUBTABLE_BREAK_,
+ self.SUBTABLE_BREAK_,
+ self.SUBTABLE_BREAK_,
+ self.SUBTABLE_BREAK_,
+ )
+ )
+
+ def equals(self, other):
+ return (
+ LookupBuilder.equals(self, other)
+ and self.glyphPairs == other.glyphPairs
+ and self.pairs == other.pairs
+ )
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the pair positioning
+ lookup.
+ """
+ builders = {}
+ builder = None
+ for glyphclass1, value1, glyphclass2, value2 in self.pairs:
+ if glyphclass1 is self.SUBTABLE_BREAK_:
+ if builder is not None:
+ builder.addSubtableBreak()
+ continue
+ valFormat1, valFormat2 = 0, 0
+ if value1:
+ valFormat1 = value1.getFormat()
+ if value2:
+ valFormat2 = value2.getFormat()
+ builder = builders.get((valFormat1, valFormat2))
+ if builder is None:
+ builder = ClassPairPosSubtableBuilder(self)
+ builders[(valFormat1, valFormat2)] = builder
+ builder.addPair(glyphclass1, value1, glyphclass2, value2)
+ subtables = []
+ if self.glyphPairs:
+ subtables.extend(buildPairPosGlyphs(self.glyphPairs, self.glyphMap))
+ for key in sorted(builders.keys()):
+ subtables.extend(builders[key].subtables())
+ return self.buildLookup_(subtables)
+
+
+class SinglePosBuilder(LookupBuilder):
+ """Builds a Single Positioning (GPOS1) lookup.
+
+ Attributes:
+ font (``fontTools.TTLib.TTFont``): A font object.
+ location: A string or tuple representing the location in the original
+ source which produced this lookup.
+ mapping: A dictionary mapping a glyph name to a ``otTables.ValueRecord``
+ objects. Usually manipulated with the :meth:`add_pos` method below.
+ lookupflag (int): The lookup's flag
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
+ an integer representing the filtering set to be used for this
+ lookup. If a mark filtering set is provided,
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
+ flags.
+ """
+
+ def __init__(self, font, location):
+ LookupBuilder.__init__(self, font, location, "GPOS", 1)
+ self.locations = {} # glyph -> (filename, line, column)
+ self.mapping = {} # glyph -> ot.ValueRecord
+
+ def add_pos(self, location, glyph, otValueRecord):
+ """Add a single positioning rule.
+
+ Args:
+ location: A string or tuple representing the location in the
+ original source which produced this lookup.
+ glyph: A glyph name.
+ otValueRection: A ``otTables.ValueRecord`` used to position the
+ glyph.
+ """
+ if not self.can_add(glyph, otValueRecord):
+ otherLoc = self.locations[glyph]
+ raise OpenTypeLibError(
+ 'Already defined different position for glyph "%s" at %s'
+ % (glyph, otherLoc),
+ location,
+ )
+ if otValueRecord:
+ self.mapping[glyph] = otValueRecord
+ self.locations[glyph] = location
+
+ def can_add(self, glyph, value):
+ assert isinstance(value, ValueRecord)
+ curValue = self.mapping.get(glyph)
+ return curValue is None or curValue == value
+
+ def equals(self, other):
+ return LookupBuilder.equals(self, other) and self.mapping == other.mapping
+
+ def build(self):
+ """Build the lookup.
+
+ Returns:
+ An ``otTables.Lookup`` object representing the single positioning
+ lookup.
+ """
+ subtables = buildSinglePos(self.mapping, self.glyphMap)
+ return self.buildLookup_(subtables)
+
+
# GSUB
def buildSingleSubstSubtable(mapping):
+ """Builds a single substitution (GSUB1) subtable.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.SingleSubstBuilder` instead.
+
+ Args:
+ mapping: A dictionary mapping input glyph names to output glyph names.
+
+ Returns:
+ An ``otTables.SingleSubst`` object, or ``None`` if the mapping dictionary
+ is empty.
+ """
if not mapping:
return None
self = ot.SingleSubst()
@@ -59,6 +1463,30 @@ def buildSingleSubstSubtable(mapping):
def buildMultipleSubstSubtable(mapping):
+ """Builds a multiple substitution (GSUB2) subtable.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.MultipleSubstBuilder` instead.
+
+ Example::
+
+ # sub uni06C0 by uni06D5.fina hamza.above
+ # sub uni06C2 by uni06C1.fina hamza.above;
+
+ subtable = buildMultipleSubstSubtable({
+ "uni06C0": [ "uni06D5.fina", "hamza.above"],
+ "uni06C2": [ "uni06D1.fina", "hamza.above"]
+ })
+
+ Args:
+ mapping: A dictionary mapping input glyph names to a list of output
+ glyph names.
+
+ Returns:
+ An ``otTables.MultipleSubst`` object or ``None`` if the mapping dictionary
+ is empty.
+ """
if not mapping:
return None
self = ot.MultipleSubst()
@@ -67,6 +1495,20 @@ def buildMultipleSubstSubtable(mapping):
def buildAlternateSubstSubtable(mapping):
+ """Builds an alternate substitution (GSUB3) subtable.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.AlternateSubstBuilder` instead.
+
+ Args:
+ mapping: A dictionary mapping input glyph names to a list of output
+ glyph names.
+
+ Returns:
+ An ``otTables.AlternateSubst`` object or ``None`` if the mapping dictionary
+ is empty.
+ """
if not mapping:
return None
self = ot.AlternateSubst()
@@ -75,20 +1517,44 @@ def buildAlternateSubstSubtable(mapping):
def _getLigatureKey(components):
- """Computes a key for ordering ligatures in a GSUB Type-4 lookup.
+ # Computes a key for ordering ligatures in a GSUB Type-4 lookup.
- When building the OpenType lookup, we need to make sure that
- the longest sequence of components is listed first, so we
- use the negative length as the primary key for sorting.
- To make buildLigatureSubstSubtable() deterministic, we use the
- component sequence as the secondary key.
+ # When building the OpenType lookup, we need to make sure that
+ # the longest sequence of components is listed first, so we
+ # use the negative length as the primary key for sorting.
+ # To make buildLigatureSubstSubtable() deterministic, we use the
+ # component sequence as the secondary key.
- For example, this will sort (f,f,f) < (f,f,i) < (f,f) < (f,i) < (f,l).
- """
+ # For example, this will sort (f,f,f) < (f,f,i) < (f,f) < (f,i) < (f,l).
return (-len(components), components)
def buildLigatureSubstSubtable(mapping):
+ """Builds a ligature substitution (GSUB4) subtable.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.LigatureSubstBuilder` instead.
+
+ Example::
+
+ # sub f f i by f_f_i;
+ # sub f i by f_i;
+
+ subtable = buildLigatureSubstSubtable({
+ ("f", "f", "i"): "f_f_i",
+ ("f", "i"): "f_i",
+ })
+
+ Args:
+ mapping: A dictionary mapping tuples of glyph names to output
+ glyph names.
+
+ Returns:
+ An ``otTables.LigatureSubst`` object or ``None`` if the mapping dictionary
+ is empty.
+ """
+
if not mapping:
return None
self = ot.LigatureSubst()
@@ -110,6 +1576,20 @@ def buildLigatureSubstSubtable(mapping):
def buildAnchor(x, y, point=None, deviceX=None, deviceY=None):
+ """Builds an Anchor table.
+
+ This determines the appropriate anchor format based on the passed parameters.
+
+ Args:
+ x (int): X coordinate.
+ y (int): Y coordinate.
+ point (int): Index of glyph contour point, if provided.
+ deviceX (``otTables.Device``): X coordinate device table, if provided.
+ deviceY (``otTables.Device``): Y coordinate device table, if provided.
+
+ Returns:
+ An ``otTables.Anchor`` object.
+ """
self = ot.Anchor()
self.XCoordinate, self.YCoordinate = x, y
self.Format = 1
@@ -117,8 +1597,9 @@ def buildAnchor(x, y, point=None, deviceX=None, deviceY=None):
self.AnchorPoint = point
self.Format = 2
if deviceX is not None or deviceY is not None:
- assert self.Format == 1, \
- "Either point, or both of deviceX/deviceY, must be None."
+ assert (
+ self.Format == 1
+ ), "Either point, or both of deviceX/deviceY, must be None."
self.XDeviceTable = deviceX
self.YDeviceTable = deviceY
self.Format = 3
@@ -126,6 +1607,31 @@ def buildAnchor(x, y, point=None, deviceX=None, deviceY=None):
def buildBaseArray(bases, numMarkClasses, glyphMap):
+ """Builds a base array record.
+
+ As part of building mark-to-base positioning rules, you will need to define
+ a ``BaseArray`` record, which "defines for each base glyph an array of
+ anchors, one for each mark class." This function builds the base array
+ subtable.
+
+ Example::
+
+ bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
+ basearray = buildBaseArray(bases, 2, font.getReverseGlyphMap())
+
+ Args:
+ bases (dict): A dictionary mapping anchors to glyphs; the keys being
+ glyph names, and the values being dictionaries mapping mark class ID
+ to the appropriate ``otTables.Anchor`` object used for attaching marks
+ of that class.
+ numMarkClasses (int): The total number of mark classes for which anchors
+ are defined.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ An ``otTables.BaseArray`` object.
+ """
self = ot.BaseArray()
self.BaseRecord = []
for base in sorted(bases, key=glyphMap.__getitem__):
@@ -137,14 +1643,27 @@ def buildBaseArray(bases, numMarkClasses, glyphMap):
def buildBaseRecord(anchors):
- """[otTables.Anchor, otTables.Anchor, ...] --> otTables.BaseRecord"""
+ # [otTables.Anchor, otTables.Anchor, ...] --> otTables.BaseRecord
self = ot.BaseRecord()
self.BaseAnchor = anchors
return self
def buildComponentRecord(anchors):
- """[otTables.Anchor, otTables.Anchor, ...] --> otTables.ComponentRecord"""
+ """Builds a component record.
+
+ As part of building mark-to-ligature positioning rules, you will need to
+ define ``ComponentRecord`` objects, which contain "an array of offsets...
+ to the Anchor tables that define all the attachment points used to attach
+ marks to the component." This function builds the component record.
+
+ Args:
+ anchors: A list of ``otTables.Anchor`` objects or ``None``.
+
+ Returns:
+ A ``otTables.ComponentRecord`` object or ``None`` if no anchors are
+ supplied.
+ """
if not anchors:
return None
self = ot.ComponentRecord()
@@ -153,7 +1672,30 @@ def buildComponentRecord(anchors):
def buildCursivePosSubtable(attach, glyphMap):
- """{"alef": (entry, exit)} --> otTables.CursivePos"""
+ """Builds a cursive positioning (GPOS3) subtable.
+
+ Cursive positioning lookups are made up of a coverage table of glyphs,
+ and a set of ``EntryExitRecord`` records containing the anchors for
+ each glyph. This function builds the cursive positioning subtable.
+
+ Example::
+
+ subtable = buildCursivePosSubtable({
+ "AlifIni": (None, buildAnchor(0, 50)),
+ "BehMed": (buildAnchor(500,250), buildAnchor(0,50)),
+ # ...
+ }, font.getReverseGlyphMap())
+
+ Args:
+ attach (dict): A mapping between glyph names and a tuple of two
+ ``otTables.Anchor`` objects representing entry and exit anchors.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ An ``otTables.CursivePos`` object, or ``None`` if the attachment
+ dictionary was empty.
+ """
if not attach:
return None
self = ot.CursivePos()
@@ -171,7 +1713,22 @@ def buildCursivePosSubtable(attach, glyphMap):
def buildDevice(deltas):
- """{8:+1, 10:-3, ...} --> otTables.Device"""
+ """Builds a Device record as part of a ValueRecord or Anchor.
+
+ Device tables specify size-specific adjustments to value records
+ and anchors to reflect changes based on the resolution of the output.
+ For example, one could specify that an anchor's Y position should be
+ increased by 1 pixel when displayed at 8 pixels per em. This routine
+ builds device records.
+
+ Args:
+ deltas: A dictionary mapping pixels-per-em sizes to the delta
+ adjustment in pixels when the font is displayed at that size.
+
+ Returns:
+ An ``otTables.Device`` object if any deltas were supplied, or
+ ``None`` otherwise.
+ """
if not deltas:
return None
self = ot.Device()
@@ -180,8 +1737,8 @@ def buildDevice(deltas):
self.EndSize = endSize = max(keys)
assert 0 <= startSize <= endSize
self.DeltaValue = deltaValues = [
- deltas.get(size, 0)
- for size in range(startSize, endSize + 1)]
+ deltas.get(size, 0) for size in range(startSize, endSize + 1)
+ ]
maxDelta = max(deltaValues)
minDelta = min(deltaValues)
assert minDelta > -129 and maxDelta < 128
@@ -195,6 +1752,36 @@ def buildDevice(deltas):
def buildLigatureArray(ligs, numMarkClasses, glyphMap):
+ """Builds a LigatureArray subtable.
+
+ As part of building a mark-to-ligature lookup, you will need to define
+ the set of anchors (for each mark class) on each component of the ligature
+ where marks can be attached. For example, for an Arabic divine name ligature
+ (lam lam heh), you may want to specify mark attachment positioning for
+ superior marks (fatha, etc.) and inferior marks (kasra, etc.) on each glyph
+ of the ligature. This routine builds the ligature array record.
+
+ Example::
+
+ buildLigatureArray({
+ "lam-lam-heh": [
+ { 0: superiorAnchor1, 1: inferiorAnchor1 }, # attach points for lam1
+ { 0: superiorAnchor2, 1: inferiorAnchor2 }, # attach points for lam2
+ { 0: superiorAnchor3, 1: inferiorAnchor3 }, # attach points for heh
+ ]
+ }, 2, font.getReverseGlyphMap())
+
+ Args:
+ ligs (dict): A mapping of ligature names to an array of dictionaries:
+ for each component glyph in the ligature, an dictionary mapping
+ mark class IDs to anchors.
+ numMarkClasses (int): The number of mark classes.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ An ``otTables.LigatureArray`` object if deltas were supplied.
+ """
self = ot.LigatureArray()
self.LigatureAttach = []
for lig in sorted(ligs, key=glyphMap.__getitem__):
@@ -207,7 +1794,7 @@ def buildLigatureArray(ligs, numMarkClasses, glyphMap):
def buildLigatureAttach(components):
- """[[Anchor, Anchor], [Anchor, Anchor, Anchor]] --> LigatureAttach"""
+ # [[Anchor, Anchor], [Anchor, Anchor, Anchor]] --> LigatureAttach
self = ot.LigatureAttach()
self.ComponentRecord = [buildComponentRecord(c) for c in components]
self.ComponentCount = len(self.ComponentRecord)
@@ -215,7 +1802,31 @@ def buildLigatureAttach(components):
def buildMarkArray(marks, glyphMap):
- """{"acute": (markClass, otTables.Anchor)} --> otTables.MarkArray"""
+ """Builds a mark array subtable.
+
+ As part of building mark-to-* positioning rules, you will need to define
+ a MarkArray subtable, which "defines the class and the anchor point
+ for a mark glyph." This function builds the mark array subtable.
+
+ Example::
+
+ mark = {
+ "acute": (0, buildAnchor(300,712)),
+ # ...
+ }
+ markarray = buildMarkArray(marks, font.getReverseGlyphMap())
+
+ Args:
+ marks (dict): A dictionary mapping anchors to glyphs; the keys being
+ glyph names, and the values being a tuple of mark class number and
+ an ``otTables.Anchor`` object representing the mark's attachment
+ point.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ An ``otTables.MarkArray`` object.
+ """
self = ot.MarkArray()
self.MarkRecord = []
for mark in sorted(marks.keys(), key=glyphMap.__getitem__):
@@ -227,11 +1838,40 @@ def buildMarkArray(marks, glyphMap):
def buildMarkBasePos(marks, bases, glyphMap):
- """Build a list of MarkBasePos subtables.
-
- a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
- marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
- bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
+ """Build a list of MarkBasePos (GPOS4) subtables.
+
+ This routine turns a set of marks and bases into a list of mark-to-base
+ positioning subtables. Currently the list will contain a single subtable
+ containing all marks and bases, although at a later date it may return the
+ optimal list of subtables subsetting the marks and bases into groups which
+ save space. See :func:`buildMarkBasePosSubtable` below.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.MarkBasePosBuilder` instead.
+
+ Example::
+
+ # a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
+
+ marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
+ bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
+ markbaseposes = buildMarkBasePos(marks, bases, font.getReverseGlyphMap())
+
+ Args:
+ marks (dict): A dictionary mapping anchors to glyphs; the keys being
+ glyph names, and the values being a tuple of mark class number and
+ an ``otTables.Anchor`` object representing the mark's attachment
+ point. (See :func:`buildMarkArray`.)
+ bases (dict): A dictionary mapping anchors to glyphs; the keys being
+ glyph names, and the values being dictionaries mapping mark class ID
+ to the appropriate ``otTables.Anchor`` object used for attaching marks
+ of that class. (See :func:`buildBaseArray`.)
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ A list of ``otTables.MarkBasePos`` objects.
"""
# TODO: Consider emitting multiple subtables to save space.
# Partition the marks and bases into disjoint subsets, so that
@@ -248,11 +1888,25 @@ def buildMarkBasePos(marks, bases, glyphMap):
def buildMarkBasePosSubtable(marks, bases, glyphMap):
- """Build a single MarkBasePos subtable.
-
- a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
- marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
- bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
+ """Build a single MarkBasePos (GPOS4) subtable.
+
+ This builds a mark-to-base lookup subtable containing all of the referenced
+ marks and bases. See :func:`buildMarkBasePos`.
+
+ Args:
+ marks (dict): A dictionary mapping anchors to glyphs; the keys being
+ glyph names, and the values being a tuple of mark class number and
+ an ``otTables.Anchor`` object representing the mark's attachment
+ point. (See :func:`buildMarkArray`.)
+ bases (dict): A dictionary mapping anchors to glyphs; the keys being
+ glyph names, and the values being dictionaries mapping mark class ID
+ to the appropriate ``otTables.Anchor`` object used for attaching marks
+ of that class. (See :func:`buildBaseArray`.)
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ A ``otTables.MarkBasePos`` object.
"""
self = ot.MarkBasePos()
self.Format = 1
@@ -265,11 +1919,50 @@ def buildMarkBasePosSubtable(marks, bases, glyphMap):
def buildMarkLigPos(marks, ligs, glyphMap):
- """Build a list of MarkLigPos subtables.
+ """Build a list of MarkLigPos (GPOS5) subtables.
+
+ This routine turns a set of marks and ligatures into a list of mark-to-ligature
+ positioning subtables. Currently the list will contain a single subtable
+ containing all marks and ligatures, although at a later date it may return
+ the optimal list of subtables subsetting the marks and ligatures into groups
+ which save space. See :func:`buildMarkLigPosSubtable` below.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.MarkLigPosBuilder` instead.
+
+ Example::
+
+ # a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
+ marks = {
+ "acute": (0, a1),
+ "grave": (0, a1),
+ "cedilla": (1, a2)
+ }
+ ligs = {
+ "f_i": [
+ { 0: a3, 1: a5 }, # f
+ { 0: a4, 1: a5 } # i
+ ],
+ # "c_t": [{...}, {...}]
+ }
+ markligposes = buildMarkLigPos(marks, ligs,
+ font.getReverseGlyphMap())
+
+ Args:
+ marks (dict): A dictionary mapping anchors to glyphs; the keys being
+ glyph names, and the values being a tuple of mark class number and
+ an ``otTables.Anchor`` object representing the mark's attachment
+ point. (See :func:`buildMarkArray`.)
+ ligs (dict): A mapping of ligature names to an array of dictionaries:
+ for each component glyph in the ligature, an dictionary mapping
+ mark class IDs to anchors. (See :func:`buildLigatureArray`.)
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ A list of ``otTables.MarkLigPos`` objects.
- a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
- marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
- ligs = {"f_i": [{0: a3, 1: a5}, {0: a4, 1: a5}], "c_t": [{...}, {...}]}
"""
# TODO: Consider splitting into multiple subtables to save space,
# as with MarkBasePos, this would be a trade-off that would need
@@ -279,11 +1972,24 @@ def buildMarkLigPos(marks, ligs, glyphMap):
def buildMarkLigPosSubtable(marks, ligs, glyphMap):
- """Build a single MarkLigPos subtable.
-
- a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
- marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
- ligs = {"f_i": [{0: a3, 1: a5}, {0: a4, 1: a5}], "c_t": [{...}, {...}]}
+ """Build a single MarkLigPos (GPOS5) subtable.
+
+ This builds a mark-to-base lookup subtable containing all of the referenced
+ marks and bases. See :func:`buildMarkLigPos`.
+
+ Args:
+ marks (dict): A dictionary mapping anchors to glyphs; the keys being
+ glyph names, and the values being a tuple of mark class number and
+ an ``otTables.Anchor`` object representing the mark's attachment
+ point. (See :func:`buildMarkArray`.)
+ ligs (dict): A mapping of ligature names to an array of dictionaries:
+ for each component glyph in the ligature, an dictionary mapping
+ mark class IDs to anchors. (See :func:`buildLigatureArray`.)
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ A ``otTables.MarkLigPos`` object.
"""
self = ot.MarkLigPos()
self.Format = 1
@@ -305,14 +2011,14 @@ def buildMarkRecord(classID, anchor):
def buildMark2Record(anchors):
- """[otTables.Anchor, otTables.Anchor, ...] --> otTables.Mark2Record"""
+ # [otTables.Anchor, otTables.Anchor, ...] --> otTables.Mark2Record
self = ot.Mark2Record()
self.Mark2Anchor = anchors
return self
def _getValueFormat(f, values, i):
- """Helper for buildPairPos{Glyphs|Classes}Subtable."""
+ # Helper for buildPairPos{Glyphs|Classes}Subtable.
if f is not None:
return f
mask = 0
@@ -322,8 +2028,45 @@ def _getValueFormat(f, values, i):
return mask
-def buildPairPosClassesSubtable(pairs, glyphMap,
- valueFormat1=None, valueFormat2=None):
+def buildPairPosClassesSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2=None):
+ """Builds a class pair adjustment (GPOS2 format 2) subtable.
+
+ Kerning tables are generally expressed as pair positioning tables using
+ class-based pair adjustments. This routine builds format 2 PairPos
+ subtables.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.ClassPairPosSubtableBuilder`
+ instead, as this takes care of ensuring that the supplied pairs can be
+ formed into non-overlapping classes and emitting individual subtables
+ whenever the non-overlapping requirement means that a new subtable is
+ required.
+
+ Example::
+
+ pairs = {}
+
+ pairs[(
+ [ "K", "X" ],
+ [ "W", "V" ]
+ )] = ( buildValue(xAdvance=+5), buildValue() )
+ # pairs[(... , ...)] = (..., ...)
+
+ pairpos = buildPairPosClassesSubtable(pairs, font.getReverseGlyphMap())
+
+ Args:
+ pairs (dict): Pair positioning data; the keys being a two-element
+ tuple of lists of glyphnames, and the values being a two-element
+ tuple of ``otTables.ValueRecord`` objects.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+ valueFormat1: Force the "left" value records to the given format.
+ valueFormat2: Force the "right" value records to the given format.
+
+ Returns:
+ A ``otTables.PairPos`` object.
+ """
coverage = set()
classDef1 = ClassDefBuilder(useClass0=True)
classDef2 = ClassDefBuilder(useClass0=False)
@@ -333,8 +2076,8 @@ def buildPairPosClassesSubtable(pairs, glyphMap,
classDef2.add(gc2)
self = ot.PairPos()
self.Format = 2
- self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0)
- self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1)
+ valueFormat1 = self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0)
+ valueFormat2 = self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1)
self.Coverage = buildCoverage(coverage, glyphMap)
self.ClassDef1 = classDef1.build()
self.ClassDef2 = classDef2.build()
@@ -347,7 +2090,9 @@ def buildPairPosClassesSubtable(pairs, glyphMap,
self.Class1Record.append(rec1)
for c2 in classes2:
rec2 = ot.Class2Record()
- rec2.Value1, rec2.Value2 = pairs.get((c1, c2), (None, None))
+ val1, val2 = pairs.get((c1, c2), (None, None))
+ rec2.Value1 = ValueRecord(src=val1, valueFormat=valueFormat1) if valueFormat1 else None
+ rec2.Value2 = ValueRecord(src=val2, valueFormat=valueFormat2) if valueFormat2 else None
rec1.Class2Record.append(rec2)
self.Class1Count = len(self.Class1Record)
self.Class2Count = len(classes2)
@@ -355,6 +2100,37 @@ def buildPairPosClassesSubtable(pairs, glyphMap,
def buildPairPosGlyphs(pairs, glyphMap):
+ """Builds a list of glyph-based pair adjustment (GPOS2 format 1) subtables.
+
+ This organises a list of pair positioning adjustments into subtables based
+ on common value record formats.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.PairPosBuilder`
+ instead.
+
+ Example::
+
+ pairs = {
+ ("K", "W"): ( buildValue(xAdvance=+5), buildValue() ),
+ ("K", "V"): ( buildValue(xAdvance=+5), buildValue() ),
+ # ...
+ }
+
+ subtables = buildPairPosGlyphs(pairs, font.getReverseGlyphMap())
+
+ Args:
+ pairs (dict): Pair positioning data; the keys being a two-element
+ tuple of glyphnames, and the values being a two-element
+ tuple of ``otTables.ValueRecord`` objects.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ A list of ``otTables.PairPos`` objects.
+ """
+
p = {} # (formatA, formatB) --> {(glyphA, glyphB): (valA, valB)}
for (glyphA, glyphB), (valA, valB) in pairs.items():
formatA = valA.getFormat() if valA is not None else 0
@@ -363,15 +2139,46 @@ def buildPairPosGlyphs(pairs, glyphMap):
pos[(glyphA, glyphB)] = (valA, valB)
return [
buildPairPosGlyphsSubtable(pos, glyphMap, formatA, formatB)
- for ((formatA, formatB), pos) in sorted(p.items())]
+ for ((formatA, formatB), pos) in sorted(p.items())
+ ]
+
+def buildPairPosGlyphsSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2=None):
+ """Builds a single glyph-based pair adjustment (GPOS2 format 1) subtable.
-def buildPairPosGlyphsSubtable(pairs, glyphMap,
- valueFormat1=None, valueFormat2=None):
+ This builds a PairPos subtable from a dictionary of glyph pairs and
+ their positioning adjustments. See also :func:`buildPairPosGlyphs`.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.PairPosBuilder` instead.
+
+ Example::
+
+ pairs = {
+ ("K", "W"): ( buildValue(xAdvance=+5), buildValue() ),
+ ("K", "V"): ( buildValue(xAdvance=+5), buildValue() ),
+ # ...
+ }
+
+ pairpos = buildPairPosGlyphsSubtable(pairs, font.getReverseGlyphMap())
+
+ Args:
+ pairs (dict): Pair positioning data; the keys being a two-element
+ tuple of glyphnames, and the values being a two-element
+ tuple of ``otTables.ValueRecord`` objects.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+ valueFormat1: Force the "left" value records to the given format.
+ valueFormat2: Force the "right" value records to the given format.
+
+ Returns:
+ A ``otTables.PairPos`` object.
+ """
self = ot.PairPos()
self.Format = 1
- self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0)
- self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1)
+ valueFormat1 = self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0)
+ valueFormat2 = self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1)
p = {}
for (glyphA, glyphB), (valA, valB) in pairs.items():
p.setdefault(glyphA, []).append((glyphB, valA, valB))
@@ -381,12 +2188,11 @@ def buildPairPosGlyphsSubtable(pairs, glyphMap,
ps = ot.PairSet()
ps.PairValueRecord = []
self.PairSet.append(ps)
- for glyph2, val1, val2 in \
- sorted(p[glyph], key=lambda x: glyphMap[x[0]]):
+ for glyph2, val1, val2 in sorted(p[glyph], key=lambda x: glyphMap[x[0]]):
pvr = ot.PairValueRecord()
pvr.SecondGlyph = glyph2
- pvr.Value1 = val1 if val1 and val1.getFormat() != 0 else None
- pvr.Value2 = val2 if val2 and val2.getFormat() != 0 else None
+ pvr.Value1 = ValueRecord(src=val1, valueFormat=valueFormat1) if valueFormat1 else None
+ pvr.Value2 = ValueRecord(src=val2, valueFormat=valueFormat2) if valueFormat2 else None
ps.PairValueRecord.append(pvr)
ps.PairValueCount = len(ps.PairValueRecord)
self.PairSetCount = len(self.PairSet)
@@ -394,7 +2200,35 @@ def buildPairPosGlyphsSubtable(pairs, glyphMap,
def buildSinglePos(mapping, glyphMap):
- """{"glyph": ValueRecord} --> [otTables.SinglePos*]"""
+ """Builds a list of single adjustment (GPOS1) subtables.
+
+ This builds a list of SinglePos subtables from a dictionary of glyph
+ names and their positioning adjustments. The format of the subtables are
+ determined to optimize the size of the resulting subtables.
+ See also :func:`buildSinglePosSubtable`.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.SinglePosBuilder` instead.
+
+ Example::
+
+ mapping = {
+ "V": buildValue({ "xAdvance" : +5 }),
+ # ...
+ }
+
+ subtables = buildSinglePos(pairs, font.getReverseGlyphMap())
+
+ Args:
+ mapping (dict): A mapping between glyphnames and
+ ``otTables.ValueRecord`` objects.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ A list of ``otTables.SinglePos`` objects.
+ """
result, handled = [], set()
# In SinglePos format 1, the covered glyphs all share the same ValueRecord.
# In format 2, each glyph has its own ValueRecord, but these records
@@ -448,13 +2282,39 @@ def buildSinglePos(mapping, glyphMap):
def buildSinglePosSubtable(values, glyphMap):
- """{glyphName: otBase.ValueRecord} --> otTables.SinglePos"""
+ """Builds a single adjustment (GPOS1) subtable.
+
+ This builds a list of SinglePos subtables from a dictionary of glyph
+ names and their positioning adjustments. The format of the subtable is
+ determined to optimize the size of the output.
+ See also :func:`buildSinglePos`.
+
+ Note that if you are implementing a layout compiler, you may find it more
+ flexible to use
+ :py:class:`fontTools.otlLib.lookupBuilders.SinglePosBuilder` instead.
+
+ Example::
+
+ mapping = {
+ "V": buildValue({ "xAdvance" : +5 }),
+ # ...
+ }
+
+ subtable = buildSinglePos(pairs, font.getReverseGlyphMap())
+
+ Args:
+ mapping (dict): A mapping between glyphnames and
+ ``otTables.ValueRecord`` objects.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ A ``otTables.SinglePos`` object.
+ """
self = ot.SinglePos()
self.Coverage = buildCoverage(values.keys(), glyphMap)
- valueRecords = [values[g] for g in self.Coverage.glyphs]
- self.ValueFormat = 0
- for v in valueRecords:
- self.ValueFormat |= v.getFormat()
+ valueFormat = self.ValueFormat = reduce(int.__or__, [v.getFormat() for v in values.values()], 0)
+ valueRecords = [ValueRecord(src=values[g], valueFormat=valueFormat) for g in self.Coverage.glyphs]
if all(v == valueRecords[0] for v in valueRecords):
self.Format = 1
if self.ValueFormat != 0:
@@ -475,7 +2335,7 @@ def _getSinglePosTableKey(subtable, glyphMap):
def _getSinglePosValueKey(valueRecord):
- """otBase.ValueRecord --> (2, ("YPlacement": 12))"""
+ # otBase.ValueRecord --> (2, ("YPlacement": 12))
assert isinstance(valueRecord, ValueRecord), valueRecord
valueFormat, result = 0, []
for name, value in valueRecord.__dict__.items():
@@ -493,17 +2353,17 @@ _DeviceTuple = namedtuple("_DeviceTuple", "DeltaFormat StartSize EndSize DeltaVa
def _makeDeviceTuple(device):
- """otTables.Device --> tuple, for making device tables unique"""
+ # otTables.Device --> tuple, for making device tables unique
return _DeviceTuple(
device.DeltaFormat,
device.StartSize,
device.EndSize,
- () if device.DeltaFormat & 0x8000 else tuple(device.DeltaValue)
+ () if device.DeltaFormat & 0x8000 else tuple(device.DeltaValue),
)
def _getSinglePosValueSize(valueKey):
- """Returns how many ushorts this valueKey (short form of ValueRecord) takes up"""
+ # Returns how many ushorts this valueKey (short form of ValueRecord) takes up
count = 0
for _, v in valueKey[1:]:
if isinstance(v, _DeviceTuple):
@@ -512,7 +2372,29 @@ def _getSinglePosValueSize(valueKey):
count += 1
return count
+
def buildValue(value):
+ """Builds a positioning value record.
+
+ Value records are used to specify coordinates and adjustments for
+ positioning and attaching glyphs. Many of the positioning functions
+ in this library take ``otTables.ValueRecord`` objects as arguments.
+ This function builds value records from dictionaries.
+
+ Args:
+ value (dict): A dictionary with zero or more of the following keys:
+ - ``xPlacement``
+ - ``yPlacement``
+ - ``xAdvance``
+ - ``yAdvance``
+ - ``xPlaDevice``
+ - ``yPlaDevice``
+ - ``xAdvDevice``
+ - ``yAdvDevice``
+
+ Returns:
+ An ``otTables.ValueRecord`` object.
+ """
self = ValueRecord()
for k, v in value.items():
setattr(self, k, v)
@@ -521,20 +2403,34 @@ def buildValue(value):
# GDEF
+
def buildAttachList(attachPoints, glyphMap):
- """{"glyphName": [4, 23]} --> otTables.AttachList, or None"""
+ """Builds an AttachList subtable.
+
+ A GDEF table may contain an Attachment Point List table (AttachList)
+ which stores the contour indices of attachment points for glyphs with
+ attachment points. This routine builds AttachList subtables.
+
+ Args:
+ attachPoints (dict): A mapping between glyph names and a list of
+ contour indices.
+
+ Returns:
+ An ``otTables.AttachList`` object if attachment points are supplied,
+ or ``None`` otherwise.
+ """
if not attachPoints:
return None
self = ot.AttachList()
self.Coverage = buildCoverage(attachPoints.keys(), glyphMap)
- self.AttachPoint = [buildAttachPoint(attachPoints[g])
- for g in self.Coverage.glyphs]
+ self.AttachPoint = [buildAttachPoint(attachPoints[g]) for g in self.Coverage.glyphs]
self.GlyphCount = len(self.AttachPoint)
return self
def buildAttachPoint(points):
- """[4, 23, 41] --> otTables.AttachPoint"""
+ # [4, 23, 41] --> otTables.AttachPoint
+ # Only used by above.
if not points:
return None
self = ot.AttachPoint()
@@ -544,7 +2440,7 @@ def buildAttachPoint(points):
def buildCaretValueForCoord(coord):
- """500 --> otTables.CaretValue, format 1"""
+ # 500 --> otTables.CaretValue, format 1
self = ot.CaretValue()
self.Format = 1
self.Coordinate = coord
@@ -552,7 +2448,7 @@ def buildCaretValueForCoord(coord):
def buildCaretValueForPoint(point):
- """4 --> otTables.CaretValue, format 2"""
+ # 4 --> otTables.CaretValue, format 2
self = ot.CaretValue()
self.Format = 2
self.CaretValuePoint = point
@@ -560,7 +2456,37 @@ def buildCaretValueForPoint(point):
def buildLigCaretList(coords, points, glyphMap):
- """{"f_f_i":[300,600]}, {"c_t":[28]} --> otTables.LigCaretList, or None"""
+ """Builds a ligature caret list table.
+
+ Ligatures appear as a single glyph representing multiple characters; however
+ when, for example, editing text containing a ``f_i`` ligature, the user may
+ want to place the cursor between the ``f`` and the ``i``. The ligature caret
+ list in the GDEF table specifies the position to display the "caret" (the
+ character insertion indicator, typically a flashing vertical bar) "inside"
+ the ligature to represent an insertion point. The insertion positions may
+ be specified either by coordinate or by contour point.
+
+ Example::
+
+ coords = {
+ "f_f_i": [300, 600] # f|fi cursor at 300 units, ff|i cursor at 600.
+ }
+ points = {
+ "c_t": [28] # c|t cursor appears at coordinate of contour point 28.
+ }
+ ligcaretlist = buildLigCaretList(coords, points, font.getReverseGlyphMap())
+
+ Args:
+ coords: A mapping between glyph names and a list of coordinates for
+ the insertion point of each ligature component after the first one.
+ points: A mapping between glyph names and a list of contour points for
+ the insertion point of each ligature component after the first one.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns:
+ A ``otTables.LigCaretList`` object if any carets are present, or
+ ``None`` otherwise."""
glyphs = set(coords.keys()) if coords else set()
if points:
glyphs.update(points.keys())
@@ -576,7 +2502,7 @@ def buildLigCaretList(coords, points, glyphMap):
def buildLigGlyph(coords, points):
- """([500], [4]) --> otTables.LigGlyph; None for empty coords/points"""
+ # ([500], [4]) --> otTables.LigGlyph; None for empty coords/points
carets = []
if coords:
carets.extend([buildCaretValueForCoord(c) for c in sorted(coords)])
@@ -591,7 +2517,30 @@ def buildLigGlyph(coords, points):
def buildMarkGlyphSetsDef(markSets, glyphMap):
- """[{"acute","grave"}, {"caron","grave"}] --> otTables.MarkGlyphSetsDef"""
+ """Builds a mark glyph sets definition table.
+
+ OpenType Layout lookups may choose to use mark filtering sets to consider
+ or ignore particular combinations of marks. These sets are specified by
+ setting a flag on the lookup, but the mark filtering sets are defined in
+ the ``GDEF`` table. This routine builds the subtable containing the mark
+ glyph set definitions.
+
+ Example::
+
+ set0 = set("acute", "grave")
+ set1 = set("caron", "grave")
+
+ markglyphsets = buildMarkGlyphSetsDef([set0, set1], font.getReverseGlyphMap())
+
+ Args:
+
+ markSets: A list of sets of glyphnames.
+ glyphMap: a glyph name to ID map, typically returned from
+ ``font.getReverseGlyphMap()``.
+
+ Returns
+ An ``otTables.MarkGlyphSetsDef`` object.
+ """
if not markSets:
return None
self = ot.MarkGlyphSetsDef()
@@ -603,6 +2552,7 @@ def buildMarkGlyphSetsDef(markSets, glyphMap):
class ClassDefBuilder(object):
"""Helper for building ClassDef tables."""
+
def __init__(self, useClass0):
self.classes_ = set()
self.glyphs_ = {}
@@ -627,7 +2577,10 @@ class ClassDefBuilder(object):
return
self.classes_.add(glyphs)
for glyph in glyphs:
- assert glyph not in self.glyphs_
+ if glyph in self.glyphs_:
+ raise OpenTypeLibError(
+ f"Glyph {glyph} is already present in class.", None
+ )
self.glyphs_[glyph] = glyphs
def classes(self):
@@ -658,3 +2611,211 @@ class ClassDefBuilder(object):
classDef = ot.ClassDef()
classDef.classDefs = glyphClasses
return classDef
+
+
+AXIS_VALUE_NEGATIVE_INFINITY = fixedToFloat(-0x80000000, 16)
+AXIS_VALUE_POSITIVE_INFINITY = fixedToFloat(0x7FFFFFFF, 16)
+
+
+def buildStatTable(ttFont, axes, locations=None, elidedFallbackName=2):
+ """Add a 'STAT' table to 'ttFont'.
+
+ 'axes' is a list of dictionaries describing axes and their
+ values.
+
+ Example::
+
+ axes = [
+ dict(
+ tag="wght",
+ name="Weight",
+ ordering=0, # optional
+ values=[
+ dict(value=100, name='Thin'),
+ dict(value=300, name='Light'),
+ dict(value=400, name='Regular', flags=0x2),
+ dict(value=900, name='Black'),
+ ],
+ )
+ ]
+
+ Each axis dict must have 'tag' and 'name' items. 'tag' maps
+ to the 'AxisTag' field. 'name' can be a name ID (int), a string,
+ or a dictionary containing multilingual names (see the
+ addMultilingualName() name table method), and will translate to
+ the AxisNameID field.
+
+ An axis dict may contain an 'ordering' item that maps to the
+ AxisOrdering field. If omitted, the order of the axes list is
+ used to calculate AxisOrdering fields.
+
+ The axis dict may contain a 'values' item, which is a list of
+ dictionaries describing AxisValue records belonging to this axis.
+
+ Each value dict must have a 'name' item, which can be a name ID
+ (int), a string, or a dictionary containing multilingual names,
+ like the axis name. It translates to the ValueNameID field.
+
+ Optionally the value dict can contain a 'flags' item. It maps to
+ the AxisValue Flags field, and will be 0 when omitted.
+
+ The format of the AxisValue is determined by the remaining contents
+ of the value dictionary:
+
+ If the value dict contains a 'value' item, an AxisValue record
+ Format 1 is created. If in addition to the 'value' item it contains
+ a 'linkedValue' item, an AxisValue record Format 3 is built.
+
+ If the value dict contains a 'nominalValue' item, an AxisValue
+ record Format 2 is built. Optionally it may contain 'rangeMinValue'
+ and 'rangeMaxValue' items. These map to -Infinity and +Infinity
+ respectively if omitted.
+
+ You cannot specify Format 4 AxisValue tables this way, as they are
+ not tied to a single axis, and specify a name for a location that
+ is defined by multiple axes values. Instead, you need to supply the
+ 'locations' argument.
+
+ The optional 'locations' argument specifies AxisValue Format 4
+ tables. It should be a list of dicts, where each dict has a 'name'
+ item, which works just like the value dicts above, an optional
+ 'flags' item (defaulting to 0x0), and a 'location' dict. A
+ location dict key is an axis tag, and the associated value is the
+ location on the specified axis. They map to the AxisIndex and Value
+ fields of the AxisValueRecord.
+
+ Example::
+
+ locations = [
+ dict(name='Regular ABCD', location=dict(wght=300, ABCD=100)),
+ dict(name='Bold ABCD XYZ', location=dict(wght=600, ABCD=200)),
+ ]
+
+ The optional 'elidedFallbackName' argument can be a name ID (int),
+ a string, a dictionary containing multilingual names, or a list of
+ STATNameStatements. It translates to the ElidedFallbackNameID field.
+
+ The 'ttFont' argument must be a TTFont instance that already has a
+ 'name' table. If a 'STAT' table already exists, it will be
+ overwritten by the newly created one.
+ """
+ ttFont["STAT"] = ttLib.newTable("STAT")
+ statTable = ttFont["STAT"].table = ot.STAT()
+ nameTable = ttFont["name"]
+ statTable.ElidedFallbackNameID = _addName(nameTable, elidedFallbackName)
+
+ # 'locations' contains data for AxisValue Format 4
+ axisRecords, axisValues = _buildAxisRecords(axes, nameTable)
+ if not locations:
+ statTable.Version = 0x00010001
+ else:
+ # We'll be adding Format 4 AxisValue records, which
+ # requires a higher table version
+ statTable.Version = 0x00010002
+ multiAxisValues = _buildAxisValuesFormat4(locations, axes, nameTable)
+ axisValues = multiAxisValues + axisValues
+
+ # Store AxisRecords
+ axisRecordArray = ot.AxisRecordArray()
+ axisRecordArray.Axis = axisRecords
+ # XXX these should not be hard-coded but computed automatically
+ statTable.DesignAxisRecordSize = 8
+ statTable.DesignAxisRecord = axisRecordArray
+ statTable.DesignAxisCount = len(axisRecords)
+
+ if axisValues:
+ # Store AxisValueRecords
+ axisValueArray = ot.AxisValueArray()
+ axisValueArray.AxisValue = axisValues
+ statTable.AxisValueArray = axisValueArray
+ statTable.AxisValueCount = len(axisValues)
+
+
+def _buildAxisRecords(axes, nameTable):
+ axisRecords = []
+ axisValues = []
+ for axisRecordIndex, axisDict in enumerate(axes):
+ axis = ot.AxisRecord()
+ axis.AxisTag = axisDict["tag"]
+ axis.AxisNameID = _addName(nameTable, axisDict["name"], 256)
+ axis.AxisOrdering = axisDict.get("ordering", axisRecordIndex)
+ axisRecords.append(axis)
+
+ for axisVal in axisDict.get("values", ()):
+ axisValRec = ot.AxisValue()
+ axisValRec.AxisIndex = axisRecordIndex
+ axisValRec.Flags = axisVal.get("flags", 0)
+ axisValRec.ValueNameID = _addName(nameTable, axisVal["name"])
+
+ if "value" in axisVal:
+ axisValRec.Value = axisVal["value"]
+ if "linkedValue" in axisVal:
+ axisValRec.Format = 3
+ axisValRec.LinkedValue = axisVal["linkedValue"]
+ else:
+ axisValRec.Format = 1
+ elif "nominalValue" in axisVal:
+ axisValRec.Format = 2
+ axisValRec.NominalValue = axisVal["nominalValue"]
+ axisValRec.RangeMinValue = axisVal.get(
+ "rangeMinValue", AXIS_VALUE_NEGATIVE_INFINITY
+ )
+ axisValRec.RangeMaxValue = axisVal.get(
+ "rangeMaxValue", AXIS_VALUE_POSITIVE_INFINITY
+ )
+ else:
+ raise ValueError("Can't determine format for AxisValue")
+
+ axisValues.append(axisValRec)
+ return axisRecords, axisValues
+
+
+def _buildAxisValuesFormat4(locations, axes, nameTable):
+ axisTagToIndex = {}
+ for axisRecordIndex, axisDict in enumerate(axes):
+ axisTagToIndex[axisDict["tag"]] = axisRecordIndex
+
+ axisValues = []
+ for axisLocationDict in locations:
+ axisValRec = ot.AxisValue()
+ axisValRec.Format = 4
+ axisValRec.ValueNameID = _addName(nameTable, axisLocationDict["name"])
+ axisValRec.Flags = axisLocationDict.get("flags", 0)
+ axisValueRecords = []
+ for tag, value in axisLocationDict["location"].items():
+ avr = ot.AxisValueRecord()
+ avr.AxisIndex = axisTagToIndex[tag]
+ avr.Value = value
+ axisValueRecords.append(avr)
+ axisValueRecords.sort(key=lambda avr: avr.AxisIndex)
+ axisValRec.AxisCount = len(axisValueRecords)
+ axisValRec.AxisValueRecord = axisValueRecords
+ axisValues.append(axisValRec)
+ return axisValues
+
+
+def _addName(nameTable, value, minNameID=0):
+ if isinstance(value, int):
+ # Already a nameID
+ return value
+ if isinstance(value, str):
+ names = dict(en=value)
+ elif isinstance(value, dict):
+ names = value
+ elif isinstance(value, list):
+ nameID = nameTable._findUnusedNameID()
+ for nameRecord in value:
+ if isinstance(nameRecord, STATNameStatement):
+ nameTable.setName(
+ nameRecord.string,
+ nameID,
+ nameRecord.platformID,
+ nameRecord.platEncID,
+ nameRecord.langID,
+ )
+ else:
+ raise TypeError("value must be a list of STATNameStatements")
+ return nameID
+ else:
+ raise TypeError("value must be int, str, dict or list")
+ return nameTable.addMultilingualName(names, minNameID=minNameID)
diff --git a/Lib/fontTools/otlLib/builder.py.sketch b/Lib/fontTools/otlLib/builder.py.sketch
new file mode 100644
index 00000000..9addf8c8
--- /dev/null
+++ b/Lib/fontTools/otlLib/builder.py.sketch
@@ -0,0 +1,105 @@
+
+from fontTools.otlLib import builder as builder
+
+GDEF::mark filtering sets
+name::
+
+lookup_flags = builder.LOOKUP_FLAG_IGNORE_MARKS | builder.LOOKUP_FLAG_RTL
+smcp_subtable = builder.buildSingleSubstitute({'a':'a.scmp'})
+smcp_lookup = builder.buildLookup([smcp_subtable], lookup_flags=lookup_flags, mark_filter_set=int)
+
+lookups = [smcp_lookup, ...]
+
+scmp_feature = builder.buildFeature('smcp', [scmp_lookup], lookup_list=lookups)
+scmp_feature = builder.buildFeature('smcp', [0])
+
+features = [smcp_feature]
+
+default_langsys = builder.buildLangSys(set([scmp_feature]), requiredFeature=None, featureOrder=features)
+default_langsys = builder.buildLangSys(set([0]), requiredFeature=None)
+
+script =
+
+
+#GSUB:
+
+builder.buildSingleSubst({'a':'a.scmp'})
+builder.buildLigatureSubst({('f','i'):'fi'})
+builder.buildMultipleSubst({'a':('a0','a1')})
+builder.buildAlternateSubst({'a':('a.0','a.1')})
+
+
+class ChainSequence : namedtuple(['backtrack', 'input', 'lookahead')])
+ pass
+
+ChainSequence(backtrack=..., input=..., lookahead=...)
+
+klass0 = frozenset()
+
+builder.buildChainContextGlyphs(
+ [
+ ( (None, ('f','f','i'), (,)), ( (1,lookup_fi), (1,lookup_2) ) ),
+ ],
+ glyphMap
+)
+builder.buildChainContextClass(
+ [
+ ( (None, (2,0,1), (,)), ( (1,lookup_fi), (1,lookup_2) ) ),
+ ],
+ klasses = ( backtrackClass, ... ),
+ glyphMap
+)
+builder.buildChainContextCoverage(
+ ( (None, (frozenset('f'),frozenset('f'),frozenset('i')), (,)), ( (1,lookup_fi), (1,lookup_2) ) ),
+ glyphMap
+)
+builder.buildExtension(...)
+
+#GPOS:
+device = builder.buildDevice()
+builder.buildAnchor(100, -200) or (100,-200)
+builder.buildAnchor(100, -200, device=device)
+builder.buildAnchor(100, -200, point=2)
+
+valueRecord = builder.buildValue({'XAdvance':-200, ...})
+
+builder.buildSinglePos({'a':valueRecord})
+builder.buildPairPosGlyphs(
+ {
+ ('a','b'): (valueRecord1,valueRecord2),
+ },
+ glyphMap,
+ , valueFormat1=None, valueFormat2=None
+)
+builder.buildPairPosClasses(
+ {
+ (frozenset(['a']),frozenset(['b'])): (valueRecord1,valueRecord2),
+ },
+ glyphMap,
+ , valueFormat1=None, valueFormat2=None
+)
+
+builder.buildCursivePos(
+ {
+ 'alef': (entry,exit),
+ }
+ glyphMap
+)
+builder.buildMarkBasePos(
+ marks = {
+ 'mark1': (klass, anchor),
+ },
+ bases = {
+ 'base0': [anchor0, anchor1, anchor2],
+ },
+ glyphMap
+)
+builder.buildMarkBasePos(
+ marks = {
+ 'mark1': (name, anchor),
+ },
+ bases = {
+ 'base0': {'top':anchor0, 'left':anchor1},
+ },
+ glyphMap
+)
diff --git a/Lib/fontTools/otlLib/error.py b/Lib/fontTools/otlLib/error.py
new file mode 100644
index 00000000..1cbef578
--- /dev/null
+++ b/Lib/fontTools/otlLib/error.py
@@ -0,0 +1,11 @@
+class OpenTypeLibError(Exception):
+ def __init__(self, message, location):
+ Exception.__init__(self, message)
+ self.location = location
+
+ def __str__(self):
+ message = Exception.__str__(self)
+ if self.location:
+ return f"{self.location}: {message}"
+ else:
+ return message
diff --git a/Lib/fontTools/otlLib/maxContextCalc.py b/Lib/fontTools/otlLib/maxContextCalc.py
index 5659310f..03e7561b 100644
--- a/Lib/fontTools/otlLib/maxContextCalc.py
+++ b/Lib/fontTools/otlLib/maxContextCalc.py
@@ -1,13 +1,11 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-
-__all__ = ['maxCtxFont']
+__all__ = ["maxCtxFont"]
def maxCtxFont(font):
"""Calculate the usMaxContext value for an entire font."""
maxCtx = 0
- for tag in ('GSUB', 'GPOS'):
+ for tag in ("GSUB", "GPOS"):
if tag not in font:
continue
table = font[tag].table
@@ -25,62 +23,59 @@ def maxCtxSubtable(maxCtx, tag, lookupType, st):
"""
# single positioning, single / multiple substitution
- if (tag == 'GPOS' and lookupType == 1) or (
- tag == 'GSUB' and lookupType in (1, 2, 3)):
+ if (tag == "GPOS" and lookupType == 1) or (
+ tag == "GSUB" and lookupType in (1, 2, 3)
+ ):
maxCtx = max(maxCtx, 1)
# pair positioning
- elif tag == 'GPOS' and lookupType == 2:
+ elif tag == "GPOS" and lookupType == 2:
maxCtx = max(maxCtx, 2)
# ligatures
- elif tag == 'GSUB' and lookupType == 4:
+ elif tag == "GSUB" and lookupType == 4:
for ligatures in st.ligatures.values():
for ligature in ligatures:
maxCtx = max(maxCtx, ligature.CompCount)
# context
- elif (tag == 'GPOS' and lookupType == 7) or (
- tag == 'GSUB' and lookupType == 5):
- maxCtx = maxCtxContextualSubtable(
- maxCtx, st, 'Pos' if tag == 'GPOS' else 'Sub')
+ elif (tag == "GPOS" and lookupType == 7) or (tag == "GSUB" and lookupType == 5):
+ maxCtx = maxCtxContextualSubtable(maxCtx, st, "Pos" if tag == "GPOS" else "Sub")
# chained context
- elif (tag == 'GPOS' and lookupType == 8) or (
- tag == 'GSUB' and lookupType == 6):
+ elif (tag == "GPOS" and lookupType == 8) or (tag == "GSUB" and lookupType == 6):
maxCtx = maxCtxContextualSubtable(
- maxCtx, st, 'Pos' if tag == 'GPOS' else 'Sub', 'Chain')
+ maxCtx, st, "Pos" if tag == "GPOS" else "Sub", "Chain"
+ )
# extensions
- elif (tag == 'GPOS' and lookupType == 9) or (
- tag == 'GSUB' and lookupType == 7):
- maxCtx = maxCtxSubtable(
- maxCtx, tag, st.ExtensionLookupType, st.ExtSubTable)
+ elif (tag == "GPOS" and lookupType == 9) or (tag == "GSUB" and lookupType == 7):
+ maxCtx = maxCtxSubtable(maxCtx, tag, st.ExtensionLookupType, st.ExtSubTable)
# reverse-chained context
- elif tag == 'GSUB' and lookupType == 8:
- maxCtx = maxCtxContextualRule(maxCtx, st, 'Reverse')
+ elif tag == "GSUB" and lookupType == 8:
+ maxCtx = maxCtxContextualRule(maxCtx, st, "Reverse")
return maxCtx
-def maxCtxContextualSubtable(maxCtx, st, ruleType, chain=''):
+def maxCtxContextualSubtable(maxCtx, st, ruleType, chain=""):
"""Calculate usMaxContext based on a contextual feature subtable."""
if st.Format == 1:
- for ruleset in getattr(st, '%s%sRuleSet' % (chain, ruleType)):
+ for ruleset in getattr(st, "%s%sRuleSet" % (chain, ruleType)):
if ruleset is None:
continue
- for rule in getattr(ruleset, '%s%sRule' % (chain, ruleType)):
+ for rule in getattr(ruleset, "%s%sRule" % (chain, ruleType)):
if rule is None:
continue
maxCtx = maxCtxContextualRule(maxCtx, rule, chain)
elif st.Format == 2:
- for ruleset in getattr(st, '%s%sClassSet' % (chain, ruleType)):
+ for ruleset in getattr(st, "%s%sClassSet" % (chain, ruleType)):
if ruleset is None:
continue
- for rule in getattr(ruleset, '%s%sClassRule' % (chain, ruleType)):
+ for rule in getattr(ruleset, "%s%sClassRule" % (chain, ruleType)):
if rule is None:
continue
maxCtx = maxCtxContextualRule(maxCtx, rule, chain)
@@ -96,6 +91,6 @@ def maxCtxContextualRule(maxCtx, st, chain):
if not chain:
return max(maxCtx, st.GlyphCount)
- elif chain == 'Reverse':
+ elif chain == "Reverse":
return max(maxCtx, st.GlyphCount + st.LookAheadGlyphCount)
return max(maxCtx, st.InputGlyphCount + st.LookAheadGlyphCount)
diff --git a/Lib/fontTools/pens/__init__.py b/Lib/fontTools/pens/__init__.py
index 3f9abc96..156cb232 100644
--- a/Lib/fontTools/pens/__init__.py
+++ b/Lib/fontTools/pens/__init__.py
@@ -1,4 +1 @@
"""Empty __init__.py file to signal Python this directory is a package."""
-
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
diff --git a/Lib/fontTools/pens/areaPen.py b/Lib/fontTools/pens/areaPen.py
index 564caa4c..403afe7b 100644
--- a/Lib/fontTools/pens/areaPen.py
+++ b/Lib/fontTools/pens/areaPen.py
@@ -1,7 +1,5 @@
"""Calculate the area of a glyph."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/basePen.py b/Lib/fontTools/pens/basePen.py
index e0d3ae06..2161e021 100644
--- a/Lib/fontTools/pens/basePen.py
+++ b/Lib/fontTools/pens/basePen.py
@@ -36,27 +36,27 @@ Coordinates are usually expressed as (x, y) tuples, but generally any
sequence of length 2 will do.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from typing import Tuple
+
from fontTools.misc.loggingTools import LogMixin
__all__ = ["AbstractPen", "NullPen", "BasePen",
"decomposeSuperBezierSegment", "decomposeQuadraticSegment"]
-class AbstractPen(object):
+class AbstractPen:
- def moveTo(self, pt):
+ def moveTo(self, pt: Tuple[float, float]) -> None:
"""Begin a new sub path, set the current point to 'pt'. You must
end each sub path with a call to pen.closePath() or pen.endPath().
"""
raise NotImplementedError
- def lineTo(self, pt):
+ def lineTo(self, pt: Tuple[float, float]) -> None:
"""Draw a straight line from the current point to 'pt'."""
raise NotImplementedError
- def curveTo(self, *points):
+ def curveTo(self, *points: Tuple[float, float]) -> None:
"""Draw a cubic bezier with an arbitrary number of control points.
The last point specified is on-curve, all others are off-curve
@@ -77,7 +77,7 @@ class AbstractPen(object):
"""
raise NotImplementedError
- def qCurveTo(self, *points):
+ def qCurveTo(self, *points: Tuple[float, float]) -> None:
"""Draw a whole string of quadratic curve segments.
The last point specified is on-curve, all others are off-curve
@@ -94,19 +94,23 @@ class AbstractPen(object):
"""
raise NotImplementedError
- def closePath(self):
+ def closePath(self) -> None:
"""Close the current sub path. You must call either pen.closePath()
or pen.endPath() after each sub path.
"""
pass
- def endPath(self):
+ def endPath(self) -> None:
"""End the current sub path, but don't close it. You must call
either pen.closePath() or pen.endPath() after each sub path.
"""
pass
- def addComponent(self, glyphName, transformation):
+ def addComponent(
+ self,
+ glyphName: str,
+ transformation: Tuple[float, float, float, float, float, float]
+ ) -> None:
"""Add a sub glyph. The 'transformation' argument must be a 6-tuple
containing an affine transformation, or a Transform object from the
fontTools.misc.transform module. More precisely: it should be a
@@ -115,7 +119,7 @@ class AbstractPen(object):
raise NotImplementedError
-class NullPen(object):
+class NullPen(AbstractPen):
"""A pen that does nothing.
"""
@@ -148,6 +152,10 @@ class LoggingPen(LogMixin, AbstractPen):
pass
+class MissingComponentError(KeyError):
+ """Indicates a component pointing to a non-existent glyph in the glyphset."""
+
+
class DecomposingPen(LoggingPen):
""" Implements a 'addComponent' method that decomposes components
@@ -156,10 +164,12 @@ class DecomposingPen(LoggingPen):
You must override moveTo, lineTo, curveTo and qCurveTo. You may
additionally override closePath, endPath and addComponent.
+
+ By default a warning message is logged when a base glyph is missing;
+ set the class variable ``skipMissingComponents`` to False if you want
+ to raise a :class:`MissingComponentError` exception.
"""
- # By default a warning message is logged when a base glyph is missing;
- # set this to False if you want to raise a 'KeyError' exception
skipMissingComponents = True
def __init__(self, glyphSet):
@@ -177,7 +187,7 @@ class DecomposingPen(LoggingPen):
glyph = self.glyphSet[glyphName]
except KeyError:
if not self.skipMissingComponents:
- raise
+ raise MissingComponentError(glyphName)
self.log.warning(
"glyph '%s' is missing from glyphSet; skipped" % glyphName)
else:
diff --git a/Lib/fontTools/pens/boundsPen.py b/Lib/fontTools/pens/boundsPen.py
index 3a103e31..810715ca 100644
--- a/Lib/fontTools/pens/boundsPen.py
+++ b/Lib/fontTools/pens/boundsPen.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.arrayTools import updateBounds, pointInRect, unionRect
from fontTools.misc.bezierTools import calcCubicBounds, calcQuadraticBounds
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/cocoaPen.py b/Lib/fontTools/pens/cocoaPen.py
index 9920ab0f..67482b4d 100644
--- a/Lib/fontTools/pens/cocoaPen.py
+++ b/Lib/fontTools/pens/cocoaPen.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/cu2quPen.py b/Lib/fontTools/pens/cu2quPen.py
new file mode 100644
index 00000000..497585bc
--- /dev/null
+++ b/Lib/fontTools/pens/cu2quPen.py
@@ -0,0 +1,257 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from fontTools.cu2qu import curve_to_quadratic
+from fontTools.pens.basePen import AbstractPen, decomposeSuperBezierSegment
+from fontTools.pens.reverseContourPen import ReverseContourPen
+from fontTools.pens.pointPen import BasePointToSegmentPen
+from fontTools.pens.pointPen import ReverseContourPointPen
+
+
+class Cu2QuPen(AbstractPen):
+ """ A filter pen to convert cubic bezier curves to quadratic b-splines
+ using the FontTools SegmentPen protocol.
+
+ other_pen: another SegmentPen used to draw the transformed outline.
+ max_err: maximum approximation error in font units. For optimal results,
+ if you know the UPEM of the font, we recommend setting this to a
+ value equal, or close to UPEM / 1000.
+ reverse_direction: flip the contours' direction but keep starting point.
+ stats: a dictionary counting the point numbers of quadratic segments.
+ ignore_single_points: don't emit contours containing only a single point
+
+ NOTE: The "ignore_single_points" argument is deprecated since v1.3.0,
+ which dropped Robofab subpport. It's no longer needed to special-case
+ UFO2-style anchors (aka "named points") when using ufoLib >= 2.0,
+ as these are no longer drawn onto pens as single-point contours,
+ but are handled separately as anchors.
+ """
+
+ def __init__(self, other_pen, max_err, reverse_direction=False,
+ stats=None, ignore_single_points=False):
+ if reverse_direction:
+ self.pen = ReverseContourPen(other_pen)
+ else:
+ self.pen = other_pen
+ self.max_err = max_err
+ self.stats = stats
+ if ignore_single_points:
+ import warnings
+ warnings.warn("ignore_single_points is deprecated and "
+ "will be removed in future versions",
+ UserWarning, stacklevel=2)
+ self.ignore_single_points = ignore_single_points
+ self.start_pt = None
+ self.current_pt = None
+
+ def _check_contour_is_open(self):
+ if self.current_pt is None:
+ raise AssertionError("moveTo is required")
+
+ def _check_contour_is_closed(self):
+ if self.current_pt is not None:
+ raise AssertionError("closePath or endPath is required")
+
+ def _add_moveTo(self):
+ if self.start_pt is not None:
+ self.pen.moveTo(self.start_pt)
+ self.start_pt = None
+
+ def moveTo(self, pt):
+ self._check_contour_is_closed()
+ self.start_pt = self.current_pt = pt
+ if not self.ignore_single_points:
+ self._add_moveTo()
+
+ def lineTo(self, pt):
+ self._check_contour_is_open()
+ self._add_moveTo()
+ self.pen.lineTo(pt)
+ self.current_pt = pt
+
+ def qCurveTo(self, *points):
+ self._check_contour_is_open()
+ n = len(points)
+ if n == 1:
+ self.lineTo(points[0])
+ elif n > 1:
+ self._add_moveTo()
+ self.pen.qCurveTo(*points)
+ self.current_pt = points[-1]
+ else:
+ raise AssertionError("illegal qcurve segment point count: %d" % n)
+
+ def _curve_to_quadratic(self, pt1, pt2, pt3):
+ curve = (self.current_pt, pt1, pt2, pt3)
+ quadratic = curve_to_quadratic(curve, self.max_err)
+ if self.stats is not None:
+ n = str(len(quadratic) - 2)
+ self.stats[n] = self.stats.get(n, 0) + 1
+ self.qCurveTo(*quadratic[1:])
+
+ def curveTo(self, *points):
+ self._check_contour_is_open()
+ n = len(points)
+ if n == 3:
+ # this is the most common case, so we special-case it
+ self._curve_to_quadratic(*points)
+ elif n > 3:
+ for segment in decomposeSuperBezierSegment(points):
+ self._curve_to_quadratic(*segment)
+ elif n == 2:
+ self.qCurveTo(*points)
+ elif n == 1:
+ self.lineTo(points[0])
+ else:
+ raise AssertionError("illegal curve segment point count: %d" % n)
+
+ def closePath(self):
+ self._check_contour_is_open()
+ if self.start_pt is None:
+ # if 'start_pt' is _not_ None, we are ignoring single-point paths
+ self.pen.closePath()
+ self.current_pt = self.start_pt = None
+
+ def endPath(self):
+ self._check_contour_is_open()
+ if self.start_pt is None:
+ self.pen.endPath()
+ self.current_pt = self.start_pt = None
+
+ def addComponent(self, glyphName, transformation):
+ self._check_contour_is_closed()
+ self.pen.addComponent(glyphName, transformation)
+
+
+class Cu2QuPointPen(BasePointToSegmentPen):
+ """ A filter pen to convert cubic bezier curves to quadratic b-splines
+ using the RoboFab PointPen protocol.
+
+ other_point_pen: another PointPen used to draw the transformed outline.
+ max_err: maximum approximation error in font units. For optimal results,
+ if you know the UPEM of the font, we recommend setting this to a
+ value equal, or close to UPEM / 1000.
+ reverse_direction: reverse the winding direction of all contours.
+ stats: a dictionary counting the point numbers of quadratic segments.
+ """
+
+ def __init__(self, other_point_pen, max_err, reverse_direction=False,
+ stats=None):
+ BasePointToSegmentPen.__init__(self)
+ if reverse_direction:
+ self.pen = ReverseContourPointPen(other_point_pen)
+ else:
+ self.pen = other_point_pen
+ self.max_err = max_err
+ self.stats = stats
+
+ def _flushContour(self, segments):
+ assert len(segments) >= 1
+ closed = segments[0][0] != "move"
+ new_segments = []
+ prev_points = segments[-1][1]
+ prev_on_curve = prev_points[-1][0]
+ for segment_type, points in segments:
+ if segment_type == 'curve':
+ for sub_points in self._split_super_bezier_segments(points):
+ on_curve, smooth, name, kwargs = sub_points[-1]
+ bcp1, bcp2 = sub_points[0][0], sub_points[1][0]
+ cubic = [prev_on_curve, bcp1, bcp2, on_curve]
+ quad = curve_to_quadratic(cubic, self.max_err)
+ if self.stats is not None:
+ n = str(len(quad) - 2)
+ self.stats[n] = self.stats.get(n, 0) + 1
+ new_points = [(pt, False, None, {}) for pt in quad[1:-1]]
+ new_points.append((on_curve, smooth, name, kwargs))
+ new_segments.append(["qcurve", new_points])
+ prev_on_curve = sub_points[-1][0]
+ else:
+ new_segments.append([segment_type, points])
+ prev_on_curve = points[-1][0]
+ if closed:
+ # the BasePointToSegmentPen.endPath method that calls _flushContour
+ # rotates the point list of closed contours so that they end with
+ # the first on-curve point. We restore the original starting point.
+ new_segments = new_segments[-1:] + new_segments[:-1]
+ self._drawPoints(new_segments)
+
+ def _split_super_bezier_segments(self, points):
+ sub_segments = []
+ # n is the number of control points
+ n = len(points) - 1
+ if n == 2:
+ # a simple bezier curve segment
+ sub_segments.append(points)
+ elif n > 2:
+ # a "super" bezier; decompose it
+ on_curve, smooth, name, kwargs = points[-1]
+ num_sub_segments = n - 1
+ for i, sub_points in enumerate(decomposeSuperBezierSegment([
+ pt for pt, _, _, _ in points])):
+ new_segment = []
+ for point in sub_points[:-1]:
+ new_segment.append((point, False, None, {}))
+ if i == (num_sub_segments - 1):
+ # the last on-curve keeps its original attributes
+ new_segment.append((on_curve, smooth, name, kwargs))
+ else:
+ # on-curves of sub-segments are always "smooth"
+ new_segment.append((sub_points[-1], True, None, {}))
+ sub_segments.append(new_segment)
+ else:
+ raise AssertionError(
+ "expected 2 control points, found: %d" % n)
+ return sub_segments
+
+ def _drawPoints(self, segments):
+ pen = self.pen
+ pen.beginPath()
+ last_offcurves = []
+ for i, (segment_type, points) in enumerate(segments):
+ if segment_type in ("move", "line"):
+ assert len(points) == 1, (
+ "illegal line segment point count: %d" % len(points))
+ pt, smooth, name, kwargs = points[0]
+ pen.addPoint(pt, segment_type, smooth, name, **kwargs)
+ elif segment_type == "qcurve":
+ assert len(points) >= 2, (
+ "illegal qcurve segment point count: %d" % len(points))
+ offcurves = points[:-1]
+ if offcurves:
+ if i == 0:
+ # any off-curve points preceding the first on-curve
+ # will be appended at the end of the contour
+ last_offcurves = offcurves
+ else:
+ for (pt, smooth, name, kwargs) in offcurves:
+ pen.addPoint(pt, None, smooth, name, **kwargs)
+ pt, smooth, name, kwargs = points[-1]
+ if pt is None:
+ # special quadratic contour with no on-curve points:
+ # we need to skip the "None" point. See also the Pen
+ # protocol's qCurveTo() method and fontTools.pens.basePen
+ pass
+ else:
+ pen.addPoint(pt, segment_type, smooth, name, **kwargs)
+ else:
+ # 'curve' segments must have been converted to 'qcurve' by now
+ raise AssertionError(
+ "unexpected segment type: %r" % segment_type)
+ for (pt, smooth, name, kwargs) in last_offcurves:
+ pen.addPoint(pt, None, smooth, name, **kwargs)
+ pen.endPath()
+
+ def addComponent(self, baseGlyphName, transformation):
+ assert self.currentPath is None
+ self.pen.addComponent(baseGlyphName, transformation)
diff --git a/Lib/fontTools/pens/filterPen.py b/Lib/fontTools/pens/filterPen.py
index 2f945507..4355ba41 100644
--- a/Lib/fontTools/pens/filterPen.py
+++ b/Lib/fontTools/pens/filterPen.py
@@ -1,13 +1,12 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import AbstractPen
+from fontTools.pens.pointPen import AbstractPointPen
from fontTools.pens.recordingPen import RecordingPen
class _PassThruComponentsMixin(object):
- def addComponent(self, glyphName, transformation):
- self._outPen.addComponent(glyphName, transformation)
+ def addComponent(self, glyphName, transformation, **kwargs):
+ self._outPen.addComponent(glyphName, transformation, **kwargs)
class FilterPen(_PassThruComponentsMixin, AbstractPen):
@@ -119,3 +118,41 @@ class ContourFilterPen(_PassThruComponentsMixin, RecordingPen):
Otherwise, the return value is drawn with the output pen.
"""
return # or return contour
+
+
+class FilterPointPen(_PassThruComponentsMixin, AbstractPointPen):
+ """ Baseclass for point pens that apply some transformation to the
+ coordinates they receive and pass them to another point pen.
+
+ You can override any of its methods. The default implementation does
+ nothing, but passes the commands unmodified to the other pen.
+
+ >>> from fontTools.pens.recordingPen import RecordingPointPen
+ >>> rec = RecordingPointPen()
+ >>> pen = FilterPointPen(rec)
+ >>> v = iter(rec.value)
+ >>> pen.beginPath(identifier="abc")
+ >>> next(v)
+ ('beginPath', (), {'identifier': 'abc'})
+ >>> pen.addPoint((1, 2), "line", False)
+ >>> next(v)
+ ('addPoint', ((1, 2), 'line', False, None), {})
+ >>> pen.addComponent("a", (2, 0, 0, 2, 10, -10), identifier="0001")
+ >>> next(v)
+ ('addComponent', ('a', (2, 0, 0, 2, 10, -10)), {'identifier': '0001'})
+ >>> pen.endPath()
+ >>> next(v)
+ ('endPath', (), {})
+ """
+
+ def __init__(self, outPointPen):
+ self._outPen = outPointPen
+
+ def beginPath(self, **kwargs):
+ self._outPen.beginPath(**kwargs)
+
+ def endPath(self):
+ self._outPen.endPath()
+
+ def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
+ self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)
diff --git a/Lib/fontTools/pens/hashPointPen.py b/Lib/fontTools/pens/hashPointPen.py
new file mode 100644
index 00000000..9aef5d87
--- /dev/null
+++ b/Lib/fontTools/pens/hashPointPen.py
@@ -0,0 +1,77 @@
+# Modified from https://github.com/adobe-type-tools/psautohint/blob/08b346865710ed3c172f1eb581d6ef243b203f99/python/psautohint/ufoFont.py#L800-L838
+import hashlib
+
+from fontTools.pens.basePen import MissingComponentError
+from fontTools.pens.pointPen import AbstractPointPen
+
+
+class HashPointPen(AbstractPointPen):
+ """
+ This pen can be used to check if a glyph's contents (outlines plus
+ components) have changed.
+
+ Components are added as the original outline plus each composite's
+ transformation.
+
+ Example: You have some TrueType hinting code for a glyph which you want to
+ compile. The hinting code specifies a hash value computed with HashPointPen
+ that was valid for the glyph's outlines at the time the hinting code was
+ written. Now you can calculate the hash for the glyph's current outlines to
+ check if the outlines have changed, which would probably make the hinting
+ code invalid.
+
+ > glyph = ufo[name]
+ > hash_pen = HashPointPen(glyph.width, ufo)
+ > glyph.drawPoints(hash_pen)
+ > ttdata = glyph.lib.get("public.truetype.instructions", None)
+ > stored_hash = ttdata.get("id", None) # The hash is stored in the "id" key
+ > if stored_hash is None or stored_hash != hash_pen.hash:
+ > logger.error(f"Glyph hash mismatch, glyph '{name}' will have no instructions in font.")
+ > else:
+ > # The hash values are identical, the outline has not changed.
+ > # Compile the hinting code ...
+ > pass
+ """
+
+ def __init__(self, glyphWidth=0, glyphSet=None):
+ self.glyphset = glyphSet
+ self.data = ["w%s" % round(glyphWidth, 9)]
+
+ @property
+ def hash(self):
+ data = "".join(self.data)
+ if len(data) >= 128:
+ data = hashlib.sha512(data.encode("ascii")).hexdigest()
+ return data
+
+ def beginPath(self, identifier=None, **kwargs):
+ pass
+
+ def endPath(self):
+ self.data.append("|")
+
+ def addPoint(
+ self,
+ pt,
+ segmentType=None,
+ smooth=False,
+ name=None,
+ identifier=None,
+ **kwargs,
+ ):
+ if segmentType is None:
+ pt_type = "o" # offcurve
+ else:
+ pt_type = segmentType[0]
+ self.data.append(f"{pt_type}{pt[0]:g}{pt[1]:+g}")
+
+ def addComponent(
+ self, baseGlyphName, transformation, identifier=None, **kwargs
+ ):
+ tr = "".join([f"{t:+}" for t in transformation])
+ self.data.append("[")
+ try:
+ self.glyphset[baseGlyphName].drawPoints(self)
+ except KeyError:
+ raise MissingComponentError(baseGlyphName)
+ self.data.append(f"({tr})]")
diff --git a/Lib/fontTools/pens/momentsPen.py b/Lib/fontTools/pens/momentsPen.py
index b83102cc..8c90f70a 100644
--- a/Lib/fontTools/pens/momentsPen.py
+++ b/Lib/fontTools/pens/momentsPen.py
@@ -1,13 +1,15 @@
"""Pen calculating 0th, 1st, and 2nd moments of area of glyph shapes.
This is low-level, autogenerated pen. Use statisticsPen instead."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
__all__ = ["MomentsPen"]
+class OpenContourError(NotImplementedError):
+ pass
+
+
class MomentsPen(BasePen):
def __init__(self, glyphset=None):
@@ -31,8 +33,9 @@ class MomentsPen(BasePen):
def _endPath(self):
p0 = self._getCurrentPoint()
if p0 != self.__startPoint:
- # Green theorem is not defined on open contours.
- raise NotImplementedError
+ raise OpenContourError(
+ "Green theorem is not defined on open contours."
+ )
def _lineTo(self, p1):
x0,y0 = self._getCurrentPoint()
diff --git a/Lib/fontTools/pens/perimeterPen.py b/Lib/fontTools/pens/perimeterPen.py
index 12e7e47e..9a09cb8f 100644
--- a/Lib/fontTools/pens/perimeterPen.py
+++ b/Lib/fontTools/pens/perimeterPen.py
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
"""Calculate the perimeter of a glyph."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
from fontTools.misc.bezierTools import approximateQuadraticArcLengthC, calcQuadraticArcLengthC, approximateCubicArcLengthC, calcCubicArcLengthC
import math
diff --git a/Lib/fontTools/pens/pointInsidePen.py b/Lib/fontTools/pens/pointInsidePen.py
index 3311841b..34597f40 100644
--- a/Lib/fontTools/pens/pointInsidePen.py
+++ b/Lib/fontTools/pens/pointInsidePen.py
@@ -2,8 +2,6 @@
for shapes.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
from fontTools.misc.bezierTools import solveQuadratic, solveCubic
diff --git a/Lib/fontTools/pens/pointPen.py b/Lib/fontTools/pens/pointPen.py
index 6299e863..26f99d41 100644
--- a/Lib/fontTools/pens/pointPen.py
+++ b/Lib/fontTools/pens/pointPen.py
@@ -11,9 +11,11 @@ steps through all the points in a call from glyph.drawPoints().
This allows the caller to provide more data for each point.
For instance, whether or not a point is smooth, and its name.
"""
-from __future__ import absolute_import, unicode_literals
-from fontTools.pens.basePen import AbstractPen
+
import math
+from typing import Any, Optional, Tuple
+
+from fontTools.pens.basePen import AbstractPen
__all__ = [
"AbstractPointPen",
@@ -25,26 +27,36 @@ __all__ = [
]
-class AbstractPointPen(object):
- """
- Baseclass for all PointPens.
- """
+class AbstractPointPen:
+ """Baseclass for all PointPens."""
- def beginPath(self, identifier=None, **kwargs):
+ def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None:
"""Start a new sub path."""
raise NotImplementedError
- def endPath(self):
+ def endPath(self) -> None:
"""End the current sub path."""
raise NotImplementedError
- def addPoint(self, pt, segmentType=None, smooth=False, name=None,
- identifier=None, **kwargs):
+ def addPoint(
+ self,
+ pt: Tuple[float, float],
+ segmentType: Optional[str] = None,
+ smooth: bool = False,
+ name: Optional[str] = None,
+ identifier: Optional[str] = None,
+ **kwargs: Any
+ ) -> None:
"""Add a point to the current sub path."""
raise NotImplementedError
- def addComponent(self, baseGlyphName, transformation, identifier=None,
- **kwargs):
+ def addComponent(
+ self,
+ baseGlyphName: str,
+ transformation: Tuple[float, float, float, float, float, float],
+ identifier: Optional[str] = None,
+ **kwargs: Any
+ ) -> None:
"""Add a sub glyph."""
raise NotImplementedError
@@ -181,18 +193,36 @@ class PointToSegmentPen(BasePointToSegmentPen):
pen.moveTo(movePt)
outputImpliedClosingLine = self.outputImpliedClosingLine
nSegments = len(segments)
+ lastPt = movePt
for i in range(nSegments):
segmentType, points = segments[i]
points = [pt for pt, smooth, name, kwargs in points]
if segmentType == "line":
assert len(points) == 1, "illegal line segment point count: %d" % len(points)
pt = points[0]
- if i + 1 != nSegments or outputImpliedClosingLine or not closed:
+ # For closed contours, a 'lineTo' is always implied from the last oncurve
+ # point to the starting point, thus we can omit it when the last and
+ # starting point don't overlap.
+ # However, when the last oncurve point is a "line" segment and has same
+ # coordinates as the starting point of a closed contour, we need to output
+ # the closing 'lineTo' explicitly (regardless of the value of the
+ # 'outputImpliedClosingLine' option) in order to disambiguate this case from
+ # the implied closing 'lineTo', otherwise the duplicate point would be lost.
+ # See https://github.com/googlefonts/fontmake/issues/572.
+ if (
+ i + 1 != nSegments
+ or outputImpliedClosingLine
+ or not closed
+ or pt == lastPt
+ ):
pen.lineTo(pt)
+ lastPt = pt
elif segmentType == "curve":
pen.curveTo(*points)
+ lastPt = points[-1]
elif segmentType == "qcurve":
pen.qCurveTo(*points)
+ lastPt = points[-1]
else:
assert 0, "illegal segmentType: %s" % segmentType
if closed:
@@ -230,9 +260,11 @@ class SegmentToPointPen(AbstractPen):
self.contour.append((pt, "move"))
def lineTo(self, pt):
+ assert self.contour is not None, "contour missing required initial moveTo"
self.contour.append((pt, "line"))
def curveTo(self, *pts):
+ assert self.contour is not None, "contour missing required initial moveTo"
for pt in pts[:-1]:
self.contour.append((pt, None))
self.contour.append((pts[-1], "curve"))
@@ -240,12 +272,15 @@ class SegmentToPointPen(AbstractPen):
def qCurveTo(self, *pts):
if pts[-1] is None:
self.contour = []
+ else:
+ assert self.contour is not None, "contour missing required initial moveTo"
for pt in pts[:-1]:
self.contour.append((pt, None))
if pts[-1] is not None:
self.contour.append((pts[-1], "qcurve"))
def closePath(self):
+ assert self.contour is not None, "contour missing required initial moveTo"
if len(self.contour) > 1 and self.contour[0][0] == self.contour[-1][0]:
self.contour[0] = self.contour[-1]
del self.contour[-1]
@@ -259,6 +294,7 @@ class SegmentToPointPen(AbstractPen):
self.contour = None
def endPath(self):
+ assert self.contour is not None, "contour missing required initial moveTo"
self._flushContour()
self.contour = None
diff --git a/Lib/fontTools/pens/qtPen.py b/Lib/fontTools/pens/qtPen.py
index 01cb37a1..34736453 100644
--- a/Lib/fontTools/pens/qtPen.py
+++ b/Lib/fontTools/pens/qtPen.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/quartzPen.py b/Lib/fontTools/pens/quartzPen.py
new file mode 100644
index 00000000..16b9c2d8
--- /dev/null
+++ b/Lib/fontTools/pens/quartzPen.py
@@ -0,0 +1,45 @@
+from fontTools.pens.basePen import BasePen
+
+from Quartz.CoreGraphics import CGPathCreateMutable, CGPathMoveToPoint
+from Quartz.CoreGraphics import CGPathAddLineToPoint, CGPathAddCurveToPoint
+from Quartz.CoreGraphics import CGPathAddQuadCurveToPoint, CGPathCloseSubpath
+
+
+__all__ = ["QuartzPen"]
+
+
+class QuartzPen(BasePen):
+
+ """A pen that creates a CGPath
+
+ Parameters
+ - path: an optional CGPath to add to
+ - xform: an optional CGAffineTransform to apply to the path
+ """
+
+ def __init__(self, glyphSet, path=None, xform=None):
+ BasePen.__init__(self, glyphSet)
+ if path is None:
+ path = CGPathCreateMutable()
+ self.path = path
+ self.xform = xform
+
+ def _moveTo(self, pt):
+ x, y = pt
+ CGPathMoveToPoint(self.path, self.xform, x, y)
+
+ def _lineTo(self, pt):
+ x, y = pt
+ CGPathAddLineToPoint(self.path, self.xform, x, y)
+
+ def _curveToOne(self, p1, p2, p3):
+ (x1, y1), (x2, y2), (x3, y3) = p1, p2, p3
+ CGPathAddCurveToPoint(self.path, self.xform, x1, y1, x2, y2, x3, y3)
+
+ def _qCurveToOne(self, p1, p2):
+ (x1, y1), (x2, y2) = p1, p2
+ CGPathAddQuadCurveToPoint(self.path, self.xform, x1, y1, x2, y2)
+
+ def _closePath(self):
+ CGPathCloseSubpath(self.path)
+
diff --git a/Lib/fontTools/pens/recordingPen.py b/Lib/fontTools/pens/recordingPen.py
index e583c8f2..99e87e5a 100644
--- a/Lib/fontTools/pens/recordingPen.py
+++ b/Lib/fontTools/pens/recordingPen.py
@@ -1,10 +1,14 @@
"""Pen recording operations that can be accessed or replayed."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import AbstractPen, DecomposingPen
+from fontTools.pens.pointPen import AbstractPointPen
-__all__ = ["replayRecording", "RecordingPen", "DecomposingRecordingPen"]
+__all__ = [
+ "replayRecording",
+ "RecordingPen",
+ "DecomposingRecordingPen",
+ "RecordingPointPen",
+]
def replayRecording(recording, pen):
@@ -90,8 +94,52 @@ class DecomposingRecordingPen(DecomposingPen, RecordingPen):
skipMissingComponents = False
+class RecordingPointPen(AbstractPointPen):
+ """PointPen recording operations that can be accessed or replayed.
+
+ The recording can be accessed as pen.value; or replayed using
+ pointPen.replay(otherPointPen).
+
+ Usage example:
+ ==============
+ from defcon import Font
+ from fontTools.pens.recordingPen import RecordingPointPen
+
+ glyph_name = 'a'
+ font_path = 'MyFont.ufo'
+
+ font = Font(font_path)
+ glyph = font[glyph_name]
+
+ pen = RecordingPointPen()
+ glyph.drawPoints(pen)
+ print(pen.value)
+
+ new_glyph = font.newGlyph('b')
+ pen.replay(new_glyph.getPointPen())
+ """
+
+ def __init__(self):
+ self.value = []
+
+ def beginPath(self, **kwargs):
+ self.value.append(("beginPath", (), kwargs))
+
+ def endPath(self):
+ self.value.append(("endPath", (), {}))
+
+ def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
+ self.value.append(("addPoint", (pt, segmentType, smooth, name), kwargs))
+
+ def addComponent(self, baseGlyphName, transformation, **kwargs):
+ self.value.append(("addComponent", (baseGlyphName, transformation), kwargs))
+
+ def replay(self, pointPen):
+ for operator, args, kwargs in self.value:
+ getattr(pointPen, operator)(*args, **kwargs)
+
+
if __name__ == "__main__":
- from fontTools.pens.basePen import _TestPen
pen = RecordingPen()
pen.moveTo((0, 0))
pen.lineTo((0, 100))
diff --git a/Lib/fontTools/pens/reportLabPen.py b/Lib/fontTools/pens/reportLabPen.py
index 2068b255..c0a4610b 100644
--- a/Lib/fontTools/pens/reportLabPen.py
+++ b/Lib/fontTools/pens/reportLabPen.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
from reportlab.graphics.shapes import Path
diff --git a/Lib/fontTools/pens/reverseContourPen.py b/Lib/fontTools/pens/reverseContourPen.py
index 27e54d74..9b3241b6 100644
--- a/Lib/fontTools/pens/reverseContourPen.py
+++ b/Lib/fontTools/pens/reverseContourPen.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.arrayTools import pairwise
from fontTools.pens.filterPen import ContourFilterPen
diff --git a/Lib/fontTools/pens/roundingPen.py b/Lib/fontTools/pens/roundingPen.py
new file mode 100644
index 00000000..2a7c476c
--- /dev/null
+++ b/Lib/fontTools/pens/roundingPen.py
@@ -0,0 +1,112 @@
+from fontTools.misc.roundTools import otRound
+from fontTools.misc.transform import Transform
+from fontTools.pens.filterPen import FilterPen, FilterPointPen
+
+
+__all__ = ["RoundingPen", "RoundingPointPen"]
+
+
+class RoundingPen(FilterPen):
+ """
+ Filter pen that rounds point coordinates and component XY offsets to integer.
+
+ >>> from fontTools.pens.recordingPen import RecordingPen
+ >>> recpen = RecordingPen()
+ >>> roundpen = RoundingPen(recpen)
+ >>> roundpen.moveTo((0.4, 0.6))
+ >>> roundpen.lineTo((1.6, 2.5))
+ >>> roundpen.qCurveTo((2.4, 4.6), (3.3, 5.7), (4.9, 6.1))
+ >>> roundpen.curveTo((6.4, 8.6), (7.3, 9.7), (8.9, 10.1))
+ >>> roundpen.addComponent("a", (1.5, 0, 0, 1.5, 10.5, -10.5))
+ >>> recpen.value == [
+ ... ('moveTo', ((0, 1),)),
+ ... ('lineTo', ((2, 3),)),
+ ... ('qCurveTo', ((2, 5), (3, 6), (5, 6))),
+ ... ('curveTo', ((6, 9), (7, 10), (9, 10))),
+ ... ('addComponent', ('a', (1.5, 0, 0, 1.5, 11, -10))),
+ ... ]
+ True
+ """
+
+ def __init__(self, outPen, roundFunc=otRound):
+ super().__init__(outPen)
+ self.roundFunc = roundFunc
+
+ def moveTo(self, pt):
+ self._outPen.moveTo((self.roundFunc(pt[0]), self.roundFunc(pt[1])))
+
+ def lineTo(self, pt):
+ self._outPen.lineTo((self.roundFunc(pt[0]), self.roundFunc(pt[1])))
+
+ def curveTo(self, *points):
+ self._outPen.curveTo(
+ *((self.roundFunc(x), self.roundFunc(y)) for x, y in points)
+ )
+
+ def qCurveTo(self, *points):
+ self._outPen.qCurveTo(
+ *((self.roundFunc(x), self.roundFunc(y)) for x, y in points)
+ )
+
+ def addComponent(self, glyphName, transformation):
+ self._outPen.addComponent(
+ glyphName,
+ Transform(
+ *transformation[:4],
+ self.roundFunc(transformation[4]),
+ self.roundFunc(transformation[5]),
+ ),
+ )
+
+
+class RoundingPointPen(FilterPointPen):
+ """
+ Filter point pen that rounds point coordinates and component XY offsets to integer.
+
+ >>> from fontTools.pens.recordingPen import RecordingPointPen
+ >>> recpen = RecordingPointPen()
+ >>> roundpen = RoundingPointPen(recpen)
+ >>> roundpen.beginPath()
+ >>> roundpen.addPoint((0.4, 0.6), 'line')
+ >>> roundpen.addPoint((1.6, 2.5), 'line')
+ >>> roundpen.addPoint((2.4, 4.6))
+ >>> roundpen.addPoint((3.3, 5.7))
+ >>> roundpen.addPoint((4.9, 6.1), 'qcurve')
+ >>> roundpen.endPath()
+ >>> roundpen.addComponent("a", (1.5, 0, 0, 1.5, 10.5, -10.5))
+ >>> recpen.value == [
+ ... ('beginPath', (), {}),
+ ... ('addPoint', ((0, 1), 'line', False, None), {}),
+ ... ('addPoint', ((2, 3), 'line', False, None), {}),
+ ... ('addPoint', ((2, 5), None, False, None), {}),
+ ... ('addPoint', ((3, 6), None, False, None), {}),
+ ... ('addPoint', ((5, 6), 'qcurve', False, None), {}),
+ ... ('endPath', (), {}),
+ ... ('addComponent', ('a', (1.5, 0, 0, 1.5, 11, -10)), {}),
+ ... ]
+ True
+ """
+
+ def __init__(self, outPen, roundFunc=otRound):
+ super().__init__(outPen)
+ self.roundFunc = roundFunc
+
+ def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
+ self._outPen.addPoint(
+ (self.roundFunc(pt[0]), self.roundFunc(pt[1])),
+ segmentType=segmentType,
+ smooth=smooth,
+ name=name,
+ **kwargs,
+ )
+
+ def addComponent(self, baseGlyphName, transformation, **kwargs):
+ self._outPen.addComponent(
+ baseGlyphName,
+ Transform(
+ *transformation[:4],
+ self.roundFunc(transformation[4]),
+ self.roundFunc(transformation[5]),
+ ),
+ **kwargs,
+ )
diff --git a/Lib/fontTools/pens/statisticsPen.py b/Lib/fontTools/pens/statisticsPen.py
index 7c2e85c8..abd6ff5e 100644
--- a/Lib/fontTools/pens/statisticsPen.py
+++ b/Lib/fontTools/pens/statisticsPen.py
@@ -1,7 +1,5 @@
"""Pen calculating area, center of mass, variance and standard-deviation,
covariance and correlation, and slant, of glyph shapes."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import math
from fontTools.pens.momentsPen import MomentsPen
diff --git a/Lib/fontTools/pens/svgPathPen.py b/Lib/fontTools/pens/svgPathPen.py
index 17e4cccc..4352ba47 100644
--- a/Lib/fontTools/pens/svgPathPen.py
+++ b/Lib/fontTools/pens/svgPathPen.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/t2CharStringPen.py b/Lib/fontTools/pens/t2CharStringPen.py
index 2025fd55..0fddec1a 100644
--- a/Lib/fontTools/pens/t2CharStringPen.py
+++ b/Lib/fontTools/pens/t2CharStringPen.py
@@ -1,38 +1,12 @@
# Copyright (c) 2009 Type Supply LLC
# Author: Tal Leming
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound, roundFunc
from fontTools.misc.psCharStrings import T2CharString
from fontTools.pens.basePen import BasePen
from fontTools.cffLib.specializer import specializeCommands, commandsToProgram
-def t2c_round(number, tolerance=0.5):
- if tolerance == 0:
- return number # no-op
- rounded = otRound(number)
- # return rounded integer if the tolerance >= 0.5, or if the absolute
- # difference between the original float and the rounded integer is
- # within the tolerance
- if tolerance >= .5 or abs(rounded - number) <= tolerance:
- return rounded
- else:
- # else return the value un-rounded
- return number
-
-def makeRoundFunc(tolerance):
- if tolerance < 0:
- raise ValueError("Rounding tolerance must be positive")
-
- def roundPoint(point):
- x, y = point
- return t2c_round(x, tolerance), t2c_round(y, tolerance)
-
- return roundPoint
-
-
class T2CharStringPen(BasePen):
"""Pen to draw Type 2 CharStrings.
@@ -46,7 +20,7 @@ class T2CharStringPen(BasePen):
def __init__(self, width, glyphSet, roundTolerance=0.5, CFF2=False):
super(T2CharStringPen, self).__init__(glyphSet)
- self.roundPoint = makeRoundFunc(roundTolerance)
+ self.round = roundFunc(roundTolerance)
self._CFF2 = CFF2
self._width = width
self._commands = []
@@ -54,7 +28,7 @@ class T2CharStringPen(BasePen):
def _p(self, pt):
p0 = self._p0
- pt = self._p0 = self.roundPoint(pt)
+ pt = self._p0 = (self.round(pt[0]), self.round(pt[1]))
return [pt[0]-p0[0], pt[1]-p0[1]]
def _moveTo(self, pt):
diff --git a/Lib/fontTools/pens/teePen.py b/Lib/fontTools/pens/teePen.py
index ce22c850..2f30e922 100644
--- a/Lib/fontTools/pens/teePen.py
+++ b/Lib/fontTools/pens/teePen.py
@@ -1,6 +1,4 @@
"""Pen multiplexing drawing to one or more pens."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import AbstractPen
diff --git a/Lib/fontTools/pens/transformPen.py b/Lib/fontTools/pens/transformPen.py
index 8f4fcd08..2dcf83b1 100644
--- a/Lib/fontTools/pens/transformPen.py
+++ b/Lib/fontTools/pens/transformPen.py
@@ -1,6 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.pens.filterPen import FilterPen
+from fontTools.pens.filterPen import FilterPen, FilterPointPen
__all__ = ["TransformPen"]
@@ -56,6 +54,51 @@ class TransformPen(FilterPen):
self._outPen.addComponent(glyphName, transformation)
+class TransformPointPen(FilterPointPen):
+ """PointPen that transforms all coordinates using a Affine transformation,
+ and passes them to another PointPen.
+
+ >>> from fontTools.pens.recordingPen import RecordingPointPen
+ >>> rec = RecordingPointPen()
+ >>> pen = TransformPointPen(rec, (2, 0, 0, 2, -10, 5))
+ >>> v = iter(rec.value)
+ >>> pen.beginPath(identifier="contour-0")
+ >>> next(v)
+ ('beginPath', (), {'identifier': 'contour-0'})
+ >>> pen.addPoint((100, 100), "line")
+ >>> next(v)
+ ('addPoint', ((190, 205), 'line', False, None), {})
+ >>> pen.endPath()
+ >>> next(v)
+ ('endPath', (), {})
+ >>> pen.addComponent("a", (1, 0, 0, 1, -10, 5), identifier="component-0")
+ >>> next(v)
+ ('addComponent', ('a', <Transform [2 0 0 2 -30 15]>), {'identifier': 'component-0'})
+ """
+
+ def __init__(self, outPointPen, transformation):
+ """The 'outPointPen' argument is another point pen object.
+ It will receive the transformed coordinates.
+ The 'transformation' argument can either be a six-tuple, or a
+ fontTools.misc.transform.Transform object.
+ """
+ super().__init__(outPointPen)
+ if not hasattr(transformation, "transformPoint"):
+ from fontTools.misc.transform import Transform
+ transformation = Transform(*transformation)
+ self._transformation = transformation
+ self._transformPoint = transformation.transformPoint
+
+ def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
+ self._outPen.addPoint(
+ self._transformPoint(pt), segmentType, smooth, name, **kwargs
+ )
+
+ def addComponent(self, baseGlyphName, transformation, **kwargs):
+ transformation = self._transformation.transform(transformation)
+ self._outPen.addComponent(baseGlyphName, transformation, **kwargs)
+
+
if __name__ == "__main__":
from fontTools.pens.basePen import _TestPen
pen = TransformPen(_TestPen(None), (2, 0, 0.5, 2, -10, 0))
diff --git a/Lib/fontTools/pens/ttGlyphPen.py b/Lib/fontTools/pens/ttGlyphPen.py
index 6e2f0d31..e7841efc 100644
--- a/Lib/fontTools/pens/ttGlyphPen.py
+++ b/Lib/fontTools/pens/ttGlyphPen.py
@@ -1,6 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from array import array
+from fontTools.misc.fixedTools import MAX_F2DOT14, otRound, floatToFixedToFloat
+from fontTools.misc.roundTools import otRound
from fontTools.pens.basePen import LoggingPen
from fontTools.pens.transformPen import TransformPen
from fontTools.ttLib.tables import ttProgram
@@ -12,30 +12,37 @@ from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
__all__ = ["TTGlyphPen"]
-# the max value that can still fit in an F2Dot14:
-# 1.99993896484375
-MAX_F2DOT14 = 0x7FFF / (1 << 14)
-
-
class TTGlyphPen(LoggingPen):
"""Pen used for drawing to a TrueType glyph.
- If `handleOverflowingTransforms` is True, the components' transform values
- are checked that they don't overflow the limits of a F2Dot14 number:
- -2.0 <= v < +2.0. If any transform value exceeds these, the composite
- glyph is decomposed.
- An exception to this rule is done for values that are very close to +2.0
- (both for consistency with the -2.0 case, and for the relative frequency
- these occur in real fonts). When almost +2.0 values occur (and all other
- values are within the range -2.0 <= x <= +2.0), they are clamped to the
- maximum positive value that can still be encoded as an F2Dot14: i.e.
- 1.99993896484375.
- If False, no check is done and all components are translated unmodified
- into the glyf table, followed by an inevitable `struct.error` once an
- attempt is made to compile them.
+ This pen can be used to construct or modify glyphs in a TrueType format
+ font. After using the pen to draw, use the ``.glyph()`` method to retrieve
+ a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
"""
def __init__(self, glyphSet, handleOverflowingTransforms=True):
+ """Construct a new pen.
+
+ Args:
+ glyphSet (ttLib._TTGlyphSet): A glyphset object, used to resolve components.
+ handleOverflowingTransforms (bool): See below.
+
+ If ``handleOverflowingTransforms`` is True, the components' transform values
+ are checked that they don't overflow the limits of a F2Dot14 number:
+ -2.0 <= v < +2.0. If any transform value exceeds these, the composite
+ glyph is decomposed.
+
+ An exception to this rule is done for values that are very close to +2.0
+ (both for consistency with the -2.0 case, and for the relative frequency
+ these occur in real fonts). When almost +2.0 values occur (and all other
+ values are within the range -2.0 <= x <= +2.0), they are clamped to the
+ maximum positive value that can still be encoded as an F2Dot14: i.e.
+ 1.99993896484375.
+
+ If False, no check is done and all components are translated unmodified
+ into the glyf table, followed by an inevitable ``struct.error`` once an
+ attempt is made to compile them.
+ """
self.glyphSet = glyphSet
self.handleOverflowingTransforms = handleOverflowingTransforms
self.init()
@@ -66,6 +73,9 @@ class TTGlyphPen(LoggingPen):
assert self._isClosed(), '"move"-type point must begin a new contour.'
self._addPoint(pt, 1)
+ def curveTo(self, *points):
+ raise NotImplementedError
+
def qCurveTo(self, *points):
assert len(points) >= 1
for pt in points[:-1]:
@@ -123,8 +133,12 @@ class TTGlyphPen(LoggingPen):
component = GlyphComponent()
component.glyphName = glyphName
- component.x, component.y = transformation[4:]
- transformation = transformation[:4]
+ component.x, component.y = (otRound(v) for v in transformation[4:])
+ # quantize floats to F2Dot14 so we get same values as when decompiled
+ # from a binary glyf table
+ transformation = tuple(
+ floatToFixedToFloat(v, 14) for v in transformation[:4]
+ )
if transformation != (1, 0, 0, 1):
if (self.handleOverflowingTransforms and
any(MAX_F2DOT14 < s <= 2 for s in transformation)):
@@ -137,12 +151,14 @@ class TTGlyphPen(LoggingPen):
return components
def glyph(self, componentFlags=0x4):
+ """Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph."""
assert self._isClosed(), "Didn't close last contour."
components = self._buildComponents(componentFlags)
glyph = Glyph()
glyph.coordinates = GlyphCoordinates(self.points)
+ glyph.coordinates.toInt()
glyph.endPtsOfContours = self.endPts
glyph.flags = array("B", self.types)
self.init()
diff --git a/Lib/fontTools/pens/wxPen.py b/Lib/fontTools/pens/wxPen.py
index 8e0e4639..1504f089 100644
--- a/Lib/fontTools/pens/wxPen.py
+++ b/Lib/fontTools/pens/wxPen.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py
index aee8a0fe..f687b056 100644
--- a/Lib/fontTools/subset/__init__.py
+++ b/Lib/fontTools/subset/__init__.py
@@ -2,21 +2,18 @@
#
# Google Author(s): Behdad Esfahbod
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound
from fontTools import ttLib
from fontTools.ttLib.tables import otTables
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.pens.basePen import NullPen
from fontTools.misc.loggingTools import Timer
from fontTools.subset.cff import *
-from fontTools.varLib import varStore
import sys
import struct
import array
import logging
-from collections import Counter
+from collections import Counter, defaultdict
from types import MethodType
__usage__ = "pyftsubset font-file [glyph...] [--option=value]..."
@@ -177,8 +174,9 @@ Glyph set expansion:
* Keep default set of features plus 'aalt', but drop 'vrt2'.
--layout-scripts[+|-]=<script>[,<script>...]
Specify (=), add to (+=) or exclude from (-=) the comma-separated
- set of OpenType layout script tags that will be preserved. By
- default all scripts are retained ('*').
+ set of OpenType layout script tags that will be preserved. LangSys tags
+ can be appended to script tag, separated by '.', for example:
+ 'arab.dflt,arab.URD,latn.TRK'. By default all scripts are retained ('*').
Hinting options:
--hinting
@@ -429,13 +427,14 @@ def intersect_class(self, glyphs, klass):
if v == klass and g in glyphs)
@_add_method(otTables.ClassDef)
-def subset(self, glyphs, remap=False):
+def subset(self, glyphs, remap=False, useClass0=True):
"""Returns ascending list of remaining classes."""
self.classDefs = {g:v for g,v in self.classDefs.items() if g in glyphs}
# Note: while class 0 has the special meaning of "not matched",
# if no glyph will ever /not match/, we can optimize class 0 out too.
+ # Only do this if allowed.
indices = _uniq_sort(
- ([0] if any(g not in self.classDefs for g in glyphs) else []) +
+ ([0] if ((not useClass0) or any(g not in self.classDefs for g in glyphs)) else []) +
list(self.classDefs.values()))
if remap:
self.remap(indices)
@@ -547,6 +546,11 @@ def prune_post_subset(self, font, options):
if not options.hinting:
# Drop device tables
self.ValueFormat &= ~0x00F0
+ # Downgrade to Format 1 if all ValueRecords are the same
+ if self.Format == 2 and all(v == self.Value[0] for v in self.Value):
+ self.Format = 1
+ self.Value = self.Value[0] if self.ValueFormat != 0 else None
+ del self.ValueCount
return True
@_add_method(otTables.PairPos)
@@ -566,15 +570,16 @@ def subset_glyphs(self, s):
self.PairSetCount = len(self.PairSet)
return bool(self.PairSetCount)
elif self.Format == 2:
- class1_map = [c for c in self.ClassDef1.subset(s.glyphs, remap=True) if c < self.Class1Count]
- class2_map = [c for c in self.ClassDef2.subset(s.glyphs, remap=True) if c < self.Class2Count]
+ class1_map = [c for c in self.ClassDef1.subset(s.glyphs.intersection(self.Coverage.glyphs), remap=True) if c < self.Class1Count]
+ class2_map = [c for c in self.ClassDef2.subset(s.glyphs, remap=True, useClass0=False) if c < self.Class2Count]
self.Class1Record = [self.Class1Record[i] for i in class1_map]
for c in self.Class1Record:
c.Class2Record = [c.Class2Record[i] for i in class2_map]
self.Class1Count = len(class1_map)
self.Class2Count = len(class2_map)
+ # If only Class2 0 left, no need to keep anything.
return bool(self.Class1Count and
- self.Class2Count and
+ (self.Class2Count > 1) and
self.Coverage.subset(s.glyphs))
else:
assert 0, "unknown format: %s" % self.Format
@@ -890,15 +895,17 @@ def __subset_classify_context(self):
self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
self.ClassDefIndex = 1 if Chain else 0
self.Input = 'Input' if Chain else 'Class'
+ elif Format == 3:
+ self.Input = 'InputCoverage' if Chain else 'Coverage'
if self.Format not in [1, 2, 3]:
return None # Don't shoot the messenger; let it go
- if not hasattr(self.__class__, "__ContextHelpers"):
- self.__class__.__ContextHelpers = {}
- if self.Format not in self.__class__.__ContextHelpers:
+ if not hasattr(self.__class__, "_subset__ContextHelpers"):
+ self.__class__._subset__ContextHelpers = {}
+ if self.Format not in self.__class__._subset__ContextHelpers:
helper = ContextHelper(self.__class__, self.Format)
- self.__class__.__ContextHelpers[self.Format] = helper
- return self.__class__.__ContextHelpers[self.Format]
+ self.__class__._subset__ContextHelpers[self.Format] = helper
+ return self.__class__._subset__ContextHelpers[self.Format]
@_add_method(otTables.ContextSubst,
otTables.ChainContextSubst)
@@ -972,6 +979,7 @@ def closure_glyphs(self, s, cur_glyphs):
if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
return []
r = self
+ input_coverages = getattr(r, c.Input)
chaos = set()
for ll in getattr(r, c.LookupRecord):
if not ll: continue
@@ -983,11 +991,11 @@ def closure_glyphs(self, s, cur_glyphs):
if seqi == 0:
pos_glyphs = frozenset(cur_glyphs)
else:
- pos_glyphs = frozenset(r.InputCoverage[seqi].intersect_glyphs(s.glyphs))
+ pos_glyphs = frozenset(input_coverages[seqi].intersect_glyphs(s.glyphs))
lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
chaos.add(seqi)
if lookup.may_have_non_1to1():
- chaos.update(range(seqi, len(r.InputCoverage)+1))
+ chaos.update(range(seqi, len(input_coverages)+1))
lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
else:
assert 0, "unknown format: %s" % self.Format
@@ -1308,14 +1316,23 @@ def subset_features(self, feature_indices):
self.ensureDecompiled()
self.SubstitutionRecord = [r for r in self.SubstitutionRecord
if r.FeatureIndex in feature_indices]
+ # remap feature indices
+ for r in self.SubstitutionRecord:
+ r.FeatureIndex = feature_indices.index(r.FeatureIndex)
self.SubstitutionCount = len(self.SubstitutionRecord)
return bool(self.SubstitutionCount)
@_add_method(otTables.FeatureVariations)
def subset_features(self, feature_indices):
self.ensureDecompiled()
- self.FeaturVariationRecord = [r for r in self.FeatureVariationRecord
- if r.FeatureTableSubstitution.subset_features(feature_indices)]
+ for r in self.FeatureVariationRecord:
+ r.FeatureTableSubstitution.subset_features(feature_indices)
+ # Prune empty records at the end only
+ # https://github.com/fonttools/fonttools/issues/1881
+ while (self.FeatureVariationRecord and
+ not self.FeatureVariationRecord[-1]
+ .FeatureTableSubstitution.SubstitutionCount):
+ self.FeatureVariationRecord.pop()
self.FeatureVariationCount = len(self.FeatureVariationRecord)
return bool(self.FeatureVariationCount)
@@ -1387,9 +1404,14 @@ def subset_glyphs(self, s):
# CBDT will inherit it
@_add_method(ttLib.getTableClass('EBDT'))
def subset_glyphs(self, s):
- self.strikeData = [{g: strike[g] for g in s.glyphs if g in strike}
- for strike in self.strikeData]
- return True
+ strikeData = [
+ {g: strike[g] for g in s.glyphs if g in strike}
+ for strike in self.strikeData
+ ]
+ # Prune empty strikes
+ # https://github.com/fonttools/fonttools/issues/1633
+ self.strikeData = [strike for strike in strikeData if strike]
+ return True
@_add_method(ttLib.getTableClass('sbix'))
def subset_glyphs(self, s):
@@ -1512,13 +1534,29 @@ def subset_feature_tags(self, feature_tags):
@_add_method(ttLib.getTableClass('GSUB'),
ttLib.getTableClass('GPOS'))
-def subset_script_tags(self, script_tags):
+def subset_script_tags(self, tags):
+ langsys = {}
+ script_tags = set()
+ for tag in tags:
+ script_tag, lang_tag = tag.split(".") if "." in tag else (tag, '*')
+ script_tags.add(script_tag.ljust(4))
+ langsys.setdefault(script_tag, set()).add(lang_tag.ljust(4))
+
if self.table.ScriptList:
self.table.ScriptList.ScriptRecord = \
[s for s in self.table.ScriptList.ScriptRecord
if s.ScriptTag in script_tags]
self.table.ScriptList.ScriptCount = len(self.table.ScriptList.ScriptRecord)
+ for record in self.table.ScriptList.ScriptRecord:
+ if record.ScriptTag in langsys and '* ' not in langsys[record.ScriptTag]:
+ record.Script.LangSysRecord = \
+ [l for l in record.Script.LangSysRecord
+ if l.LangSysTag in langsys[record.ScriptTag]]
+ record.Script.LangSysCount = len(record.Script.LangSysRecord)
+ if "dflt" not in langsys[record.ScriptTag]:
+ record.Script.DefaultLangSys = None
+
@_add_method(ttLib.getTableClass('GSUB'),
ttLib.getTableClass('GPOS'))
def prune_features(self):
@@ -1613,11 +1651,15 @@ def prune_post_subset(self, font, options):
#if table.ScriptList and not table.ScriptList.ScriptRecord:
# table.ScriptList = None
- if not table.FeatureList and hasattr(table, 'FeatureVariations'):
- table.FeatureVariations = None
+ if hasattr(table, 'FeatureVariations'):
+ # drop FeatureVariations if there are no features to substitute
+ if table.FeatureVariations and not (
+ table.FeatureList and table.FeatureVariations.FeatureVariationRecord
+ ):
+ table.FeatureVariations = None
- if hasattr(table, 'FeatureVariations') and not table.FeatureVariations:
- if table.Version == 0x00010001:
+ # downgrade table version if there are no FeatureVariations
+ if not table.FeatureVariations and table.Version == 0x00010001:
table.Version = 0x00010000
return True
@@ -1836,7 +1878,7 @@ def subset_glyphs(self, s):
table.RsbMap.mapping = _dict_subset(table.RsbMap.mapping, s.glyphs)
used.update(table.RsbMap.mapping.values())
- varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used, retainFirstMap=retainAdvMap, advIdxes=advIdxes_)
+ varidx_map = table.VarStore.subset_varidxes(used, retainFirstMap=retainAdvMap, advIdxes=advIdxes_)
if table.AdvWidthMap:
table.AdvWidthMap.mapping = _remap_index_map(s, varidx_map, table.AdvWidthMap)
@@ -1874,7 +1916,7 @@ def subset_glyphs(self, s):
table.VOrgMap.mapping = _dict_subset(table.VOrgMap.mapping, s.glyphs)
used.update(table.VOrgMap.mapping.values())
- varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used, retainFirstMap=retainAdvMap, advIdxes=advIdxes_)
+ varidx_map = table.VarStore.subset_varidxes(used, retainFirstMap=retainAdvMap, advIdxes=advIdxes_)
if table.AdvHeightMap:
table.AdvHeightMap.mapping = _remap_index_map(s, varidx_map, table.AdvHeightMap)
@@ -1942,27 +1984,130 @@ def subset_glyphs(self, s):
else:
assert False, "unknown 'prop' format %s" % prop.Format
+def _paint_glyph_names(paint, colr):
+ result = set()
+
+ def callback(paint):
+ if paint.Format in {
+ otTables.PaintFormat.PaintGlyph,
+ otTables.PaintFormat.PaintColrGlyph,
+ }:
+ result.add(paint.Glyph)
+
+ paint.traverse(colr, callback)
+ return result
+
@_add_method(ttLib.getTableClass('COLR'))
def closure_glyphs(self, s):
+ if self.version > 0:
+ # on decompiling COLRv1, we only keep around the raw otTables
+ # but for subsetting we need dicts with fully decompiled layers;
+ # we store them temporarily in the C_O_L_R_ instance and delete
+ # them after we have finished subsetting.
+ self.ColorLayers = self._decompileColorLayersV0(self.table)
+ self.ColorLayersV1 = {
+ rec.BaseGlyph: rec.Paint
+ for rec in self.table.BaseGlyphV1List.BaseGlyphV1Record
+ }
+
decompose = s.glyphs
while decompose:
layers = set()
for g in decompose:
- for l in self.ColorLayers.get(g, []):
- layers.add(l.name)
+ for layer in self.ColorLayers.get(g, []):
+ layers.add(layer.name)
+
+ if self.version > 0:
+ paint = self.ColorLayersV1.get(g)
+ if paint is not None:
+ layers.update(_paint_glyph_names(paint, self.table))
+
layers -= s.glyphs
s.glyphs.update(layers)
decompose = layers
@_add_method(ttLib.getTableClass('COLR'))
def subset_glyphs(self, s):
+ from fontTools.colorLib.unbuilder import unbuildColrV1
+ from fontTools.colorLib.builder import buildColrV1, populateCOLRv0
+
self.ColorLayers = {g: self.ColorLayers[g] for g in s.glyphs if g in self.ColorLayers}
- return bool(self.ColorLayers)
+ if self.version == 0:
+ return bool(self.ColorLayers)
+
+ colorGlyphsV1 = unbuildColrV1(self.table.LayerV1List, self.table.BaseGlyphV1List)
+ self.table.LayerV1List, self.table.BaseGlyphV1List = buildColrV1(
+ {g: colorGlyphsV1[g] for g in colorGlyphsV1 if g in s.glyphs}
+ )
+ del self.ColorLayersV1
+
+ layersV0 = self.ColorLayers
+ if not self.table.BaseGlyphV1List.BaseGlyphV1Record:
+ # no more COLRv1 glyphs: downgrade to version 0
+ self.version = 0
+ del self.table
+ return bool(layersV0)
+
+ if layersV0:
+ populateCOLRv0(
+ self.table,
+ {
+ g: [(layer.name, layer.colorID) for layer in layersV0[g]]
+ for g in layersV0
+ },
+ )
+ del self.ColorLayers
+
+ # TODO: also prune ununsed varIndices in COLR.VarStore
+ return True
-# TODO: prune unused palettes
@_add_method(ttLib.getTableClass('CPAL'))
def prune_post_subset(self, font, options):
- return True
+ colr = font.get("COLR")
+ if not colr: # drop CPAL if COLR was subsetted to empty
+ return False
+
+ colors_by_index = defaultdict(list)
+
+ def collect_colors_by_index(paint):
+ if hasattr(paint, "Color"): # either solid colors...
+ colors_by_index[paint.Color.PaletteIndex].append(paint.Color)
+ elif hasattr(paint, "ColorLine"): # ... or gradient color stops
+ for stop in paint.ColorLine.ColorStop:
+ colors_by_index[stop.Color.PaletteIndex].append(stop.Color)
+
+ if colr.version == 0:
+ for layers in colr.ColorLayers.values():
+ for layer in layers:
+ colors_by_index[layer.colorID].append(layer)
+ else:
+ if colr.table.LayerRecordArray:
+ for layer in colr.table.LayerRecordArray.LayerRecord:
+ colors_by_index[layer.PaletteIndex].append(layer)
+ for record in colr.table.BaseGlyphV1List.BaseGlyphV1Record:
+ record.Paint.traverse(colr.table, collect_colors_by_index)
+
+ retained_palette_indices = set(colors_by_index.keys())
+ for palette in self.palettes:
+ palette[:] = [c for i, c in enumerate(palette) if i in retained_palette_indices]
+ assert len(palette) == len(retained_palette_indices)
+
+ for new_index, old_index in enumerate(sorted(retained_palette_indices)):
+ for record in colors_by_index[old_index]:
+ if hasattr(record, "colorID"): # v0
+ record.colorID = new_index
+ elif hasattr(record, "PaletteIndex"): # v1
+ record.PaletteIndex = new_index
+ else:
+ raise AssertionError(record)
+
+ self.numPaletteEntries = len(self.palettes[0])
+
+ if self.version == 1:
+ self.paletteEntryLabels = [
+ label for i, label in self.paletteEntryLabels if i in retained_palette_indices
+ ]
+ return bool(self.numPaletteEntries)
@_add_method(otTables.MathGlyphConstruction)
def closure_glyphs(self, glyphs):
@@ -2076,7 +2221,7 @@ def remapComponentsFast(self, glyphidmap):
elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
more = flags & 0x0020 # MORE_COMPONENTS
- self.data = data.tostring()
+ self.data = data.tobytes()
@_add_method(ttLib.getTableClass('glyf'))
def closure_glyphs(self, s):
@@ -2166,7 +2311,17 @@ def prune_pre_subset(self, font, options):
@_add_method(ttLib.getTableClass('cmap'))
def subset_glyphs(self, s):
s.glyphs = None # We use s.glyphs_requested and s.unicodes_requested only
+
+ tables_format12_bmp = []
+ table_plat0_enc3 = {} # Unicode platform, Unicode BMP only, keyed by language
+ table_plat3_enc1 = {} # Windows platform, Unicode BMP, keyed by language
+
for t in self.tables:
+ if t.platformID == 0 and t.platEncID == 3:
+ table_plat0_enc3[t.language] = t
+ if t.platformID == 3 and t.platEncID == 1:
+ table_plat3_enc1[t.language] = t
+
if t.format == 14:
# TODO(behdad) We drop all the default-UVS mappings
# for glyphs_requested. So it's the caller's responsibility to make
@@ -2178,16 +2333,38 @@ def subset_glyphs(self, s):
elif t.isUnicode():
t.cmap = {u:g for u,g in t.cmap.items()
if g in s.glyphs_requested or u in s.unicodes_requested}
+ # Collect format 12 tables that hold only basic multilingual plane
+ # codepoints.
+ if t.format == 12 and t.cmap and max(t.cmap.keys()) < 0x10000:
+ tables_format12_bmp.append(t)
else:
t.cmap = {u:g for u,g in t.cmap.items()
if g in s.glyphs_requested}
+
+ # Fomat 12 tables are redundant if they contain just the same BMP codepoints
+ # their little BMP-only encoding siblings contain.
+ for t in tables_format12_bmp:
+ if (
+ t.platformID == 0 # Unicode platform
+ and t.platEncID == 4 # Unicode full repertoire
+ and t.language in table_plat0_enc3 # Have a BMP-only sibling?
+ and table_plat0_enc3[t.language].cmap == t.cmap
+ ):
+ t.cmap.clear()
+ elif (
+ t.platformID == 3 # Windows platform
+ and t.platEncID == 10 # Unicode full repertoire
+ and t.language in table_plat3_enc1 # Have a BMP-only sibling?
+ and table_plat3_enc1[t.language].cmap == t.cmap
+ ):
+ t.cmap.clear()
+
self.tables = [t for t in self.tables
if (t.cmap if t.format != 14 else t.uvsDict)]
self.numSubTables = len(self.tables)
# TODO(behdad) Convert formats when needed.
# In particular, if we have a format=12 without non-BMP
- # characters, either drop format=12 one or convert it
- # to format=4 if there's not one.
+ # characters, convert it to format=4 if there's not one.
return True # Required table
@_add_method(ttLib.getTableClass('DSIG'))
@@ -2687,7 +2864,7 @@ class Subsetter(object):
def load_font(fontFile,
options,
allowVID=False,
- checkChecksums=False,
+ checkChecksums=0,
dontLoadGlyphNames=False,
lazy=True):
@@ -2760,6 +2937,7 @@ def usage():
@timer("make one with everything (TOTAL TIME)")
def main(args=None):
+ """OpenType font subsetter and optimizer"""
from os.path import splitext
from fontTools import configLogger
diff --git a/Lib/fontTools/subset/__main__.py b/Lib/fontTools/subset/__main__.py
index 10e470fe..22038473 100644
--- a/Lib/fontTools/subset/__main__.py
+++ b/Lib/fontTools/subset/__main__.py
@@ -1,7 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import sys
from fontTools.subset import main
+
if __name__ == '__main__':
sys.exit(main())
diff --git a/Lib/fontTools/subset/cff.py b/Lib/fontTools/subset/cff.py
index 7db6d880..b59c6b96 100644
--- a/Lib/fontTools/subset/cff.py
+++ b/Lib/fontTools/subset/cff.py
@@ -1,7 +1,7 @@
from fontTools.misc import psCharStrings
from fontTools import ttLib
from fontTools.pens.basePen import NullPen
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound
from fontTools.varLib.varStore import VarStoreInstancer
def _add_method(*clazzes):
diff --git a/Lib/fontTools/svgLib/__init__.py b/Lib/fontTools/svgLib/__init__.py
index b301a3ba..c049006b 100644
--- a/Lib/fontTools/svgLib/__init__.py
+++ b/Lib/fontTools/svgLib/__init__.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
from .path import SVGPath, parse_path
__all__ = ["SVGPath", "parse_path"]
diff --git a/Lib/fontTools/svgLib/path/__init__.py b/Lib/fontTools/svgLib/path/__init__.py
index 5dd3329c..9440429b 100644
--- a/Lib/fontTools/svgLib/path/__init__.py
+++ b/Lib/fontTools/svgLib/path/__init__.py
@@ -1,6 +1,4 @@
-from __future__ import (
- print_function, division, absolute_import, unicode_literals)
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tostr
from fontTools.pens.transformPen import TransformPen
from fontTools.misc import etree
diff --git a/Lib/fontTools/svgLib/path/arc.py b/Lib/fontTools/svgLib/path/arc.py
index 38d1ea9c..31810712 100644
--- a/Lib/fontTools/svgLib/path/arc.py
+++ b/Lib/fontTools/svgLib/path/arc.py
@@ -4,11 +4,8 @@ The code is mostly adapted from Blink's SVGPathNormalizer::DecomposeArcToCubic
https://github.com/chromium/chromium/blob/93831f2/third_party/
blink/renderer/core/svg/svg_path_parser.cc#L169-L278
"""
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
-from fontTools.misc.py23 import isfinite
from fontTools.misc.transform import Identity, Scale
-from math import atan2, ceil, cos, fabs, pi, radians, sin, sqrt, tan
+from math import atan2, ceil, cos, fabs, isfinite, pi, radians, sin, sqrt, tan
TWO_PI = 2 * pi
diff --git a/Lib/fontTools/svgLib/path/parser.py b/Lib/fontTools/svgLib/path/parser.py
index bdf3de0c..1fcf8998 100644
--- a/Lib/fontTools/svgLib/path/parser.py
+++ b/Lib/fontTools/svgLib/path/parser.py
@@ -7,26 +7,86 @@
# Copyright (c) 2013-2014 Lennart Regebro
# License: MIT
-from __future__ import (
- print_function, division, absolute_import, unicode_literals)
-from fontTools.misc.py23 import *
from .arc import EllipticalArc
import re
COMMANDS = set('MmZzLlHhVvCcSsQqTtAa')
+ARC_COMMANDS = set("Aa")
UPPERCASE = set('MZLHVCSQTA')
COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])")
-FLOAT_RE = re.compile(r"[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?")
+FLOAT_RE = re.compile(
+ r"[-+]?" # optional sign
+ r"(?:"
+ r"(?:0|[1-9][0-9]*)(?:\.[0-9]+(?:[eE][-+]?[0-9]+)?)?" # int/float
+ r"|"
+ r"(?:\.[0-9]+(?:[eE][-+]?[0-9]+)?)" # float with leading dot (e.g. '.42')
+ r")"
+)
+BOOL_RE = re.compile("^[01]")
+SEPARATOR_RE = re.compile(f"[, \t]")
def _tokenize_path(pathdef):
+ arc_cmd = None
for x in COMMAND_RE.split(pathdef):
if x in COMMANDS:
+ arc_cmd = x if x in ARC_COMMANDS else None
yield x
- for token in FLOAT_RE.findall(x):
- yield token
+ continue
+
+ if arc_cmd:
+ try:
+ yield from _tokenize_arc_arguments(x)
+ except ValueError as e:
+ raise ValueError(f"Invalid arc command: '{arc_cmd}{x}'") from e
+ else:
+ for token in FLOAT_RE.findall(x):
+ yield token
+
+
+ARC_ARGUMENT_TYPES = (
+ ("rx", FLOAT_RE),
+ ("ry", FLOAT_RE),
+ ("x-axis-rotation", FLOAT_RE),
+ ("large-arc-flag", BOOL_RE),
+ ("sweep-flag", BOOL_RE),
+ ("x", FLOAT_RE),
+ ("y", FLOAT_RE),
+)
+
+
+def _tokenize_arc_arguments(arcdef):
+ raw_args = [s for s in SEPARATOR_RE.split(arcdef) if s]
+ if not raw_args:
+ raise ValueError(f"Not enough arguments: '{arcdef}'")
+ raw_args.reverse()
+
+ i = 0
+ while raw_args:
+ arg = raw_args.pop()
+
+ name, pattern = ARC_ARGUMENT_TYPES[i]
+ match = pattern.search(arg)
+ if not match:
+ raise ValueError(f"Invalid argument for '{name}' parameter: {arg!r}")
+
+ j, k = match.span()
+ yield arg[j:k]
+ arg = arg[k:]
+
+ if arg:
+ raw_args.append(arg)
+
+ # wrap around every 7 consecutive arguments
+ if i == 6:
+ i = 0
+ else:
+ i += 1
+
+ if i != 0:
+ raise ValueError(f"Not enough arguments: '{arcdef}'")
def parse_path(pathdef, pen, current_pos=(0, 0), arc_class=EllipticalArc):
diff --git a/Lib/fontTools/t1Lib/__init__.py b/Lib/fontTools/t1Lib/__init__.py
index 142cedd7..e1d94d35 100644
--- a/Lib/fontTools/t1Lib/__init__.py
+++ b/Lib/fontTools/t1Lib/__init__.py
@@ -15,8 +15,7 @@ write(path, data, kind='OTHER', dohex=False)
part should be written as hexadecimal or binary, but only if kind
is 'OTHER'.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin
from fontTools.misc import eexec
from fontTools.misc.macCreatorType import getMacCreatorAndType
import os
diff --git a/Lib/fontTools/ttLib/__init__.py b/Lib/fontTools/ttLib/__init__.py
index a3ab303c..16417e73 100644
--- a/Lib/fontTools/ttLib/__init__.py
+++ b/Lib/fontTools/ttLib/__init__.py
@@ -41,8 +41,6 @@ Dumping 'prep' table...
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.loggingTools import deprecateFunction
import logging
diff --git a/Lib/fontTools/ttLib/macUtils.py b/Lib/fontTools/ttLib/macUtils.py
index 17dd5efe..496fb672 100644
--- a/Lib/fontTools/ttLib/macUtils.py
+++ b/Lib/fontTools/ttLib/macUtils.py
@@ -1,6 +1,5 @@
"""ttLib.macUtils.py -- Various Mac-specific stuff."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from io import BytesIO
from fontTools.misc.macRes import ResourceReader, ResourceError
@@ -41,7 +40,7 @@ class SFNTResourceReader(BytesIO):
def __init__(self, path, res_name_or_index):
from fontTools import ttLib
reader = ResourceReader(path)
- if isinstance(res_name_or_index, basestring):
+ if isinstance(res_name_or_index, str):
rsrc = reader.getNamedResource('sfnt', res_name_or_index)
else:
rsrc = reader.getIndResource('sfnt', res_name_or_index)
diff --git a/Lib/fontTools/ttLib/removeOverlaps.py b/Lib/fontTools/ttLib/removeOverlaps.py
new file mode 100644
index 00000000..fb5c77ab
--- /dev/null
+++ b/Lib/fontTools/ttLib/removeOverlaps.py
@@ -0,0 +1,192 @@
+""" Simplify TrueType glyphs by merging overlapping contours/components.
+
+Requires https://github.com/fonttools/skia-pathops
+"""
+
+import itertools
+import logging
+from typing import Iterable, Optional, Mapping
+
+from fontTools.ttLib import ttFont
+from fontTools.ttLib.tables import _g_l_y_f
+from fontTools.ttLib.tables import _h_m_t_x
+from fontTools.pens.ttGlyphPen import TTGlyphPen
+
+import pathops
+
+
+__all__ = ["removeOverlaps"]
+
+
+log = logging.getLogger("fontTools.ttLib.removeOverlaps")
+
+_TTGlyphMapping = Mapping[str, ttFont._TTGlyph]
+
+
+def skPathFromGlyph(glyphName: str, glyphSet: _TTGlyphMapping) -> pathops.Path:
+ path = pathops.Path()
+ pathPen = path.getPen(glyphSet=glyphSet)
+ glyphSet[glyphName].draw(pathPen)
+ return path
+
+
+def skPathFromGlyphComponent(
+ component: _g_l_y_f.GlyphComponent, glyphSet: _TTGlyphMapping
+):
+ baseGlyphName, transformation = component.getComponentInfo()
+ path = skPathFromGlyph(baseGlyphName, glyphSet)
+ return path.transform(*transformation)
+
+
+def componentsOverlap(glyph: _g_l_y_f.Glyph, glyphSet: _TTGlyphMapping) -> bool:
+ if not glyph.isComposite():
+ raise ValueError("This method only works with TrueType composite glyphs")
+ if len(glyph.components) < 2:
+ return False # single component, no overlaps
+
+ component_paths = {}
+
+ def _get_nth_component_path(index: int) -> pathops.Path:
+ if index not in component_paths:
+ component_paths[index] = skPathFromGlyphComponent(
+ glyph.components[index], glyphSet
+ )
+ return component_paths[index]
+
+ return any(
+ pathops.op(
+ _get_nth_component_path(i),
+ _get_nth_component_path(j),
+ pathops.PathOp.INTERSECTION,
+ fix_winding=False,
+ keep_starting_points=False,
+ )
+ for i, j in itertools.combinations(range(len(glyph.components)), 2)
+ )
+
+
+def ttfGlyphFromSkPath(path: pathops.Path) -> _g_l_y_f.Glyph:
+ # Skia paths have no 'components', no need for glyphSet
+ ttPen = TTGlyphPen(glyphSet=None)
+ path.draw(ttPen)
+ glyph = ttPen.glyph()
+ assert not glyph.isComposite()
+ # compute glyph.xMin (glyfTable parameter unused for non composites)
+ glyph.recalcBounds(glyfTable=None)
+ return glyph
+
+
+def removeTTGlyphOverlaps(
+ glyphName: str,
+ glyphSet: _TTGlyphMapping,
+ glyfTable: _g_l_y_f.table__g_l_y_f,
+ hmtxTable: _h_m_t_x.table__h_m_t_x,
+ removeHinting: bool = True,
+) -> bool:
+ glyph = glyfTable[glyphName]
+ # decompose composite glyphs only if components overlap each other
+ if (
+ glyph.numberOfContours > 0
+ or glyph.isComposite()
+ and componentsOverlap(glyph, glyphSet)
+ ):
+ path = skPathFromGlyph(glyphName, glyphSet)
+
+ # remove overlaps
+ path2 = pathops.simplify(path, clockwise=path.clockwise)
+
+ # replace TTGlyph if simplified path is different (ignoring contour order)
+ if {tuple(c) for c in path.contours} != {tuple(c) for c in path2.contours}:
+ glyfTable[glyphName] = glyph = ttfGlyphFromSkPath(path2)
+ # simplified glyph is always unhinted
+ assert not glyph.program
+ # also ensure hmtx LSB == glyph.xMin so glyph origin is at x=0
+ width, lsb = hmtxTable[glyphName]
+ if lsb != glyph.xMin:
+ hmtxTable[glyphName] = (width, glyph.xMin)
+ return True
+
+ if removeHinting:
+ glyph.removeHinting()
+ return False
+
+
+def removeOverlaps(
+ font: ttFont.TTFont,
+ glyphNames: Optional[Iterable[str]] = None,
+ removeHinting: bool = True,
+) -> None:
+ """Simplify glyphs in TTFont by merging overlapping contours.
+
+ Overlapping components are first decomposed to simple contours, then merged.
+
+ Currently this only works with TrueType fonts with 'glyf' table.
+ Raises NotImplementedError if 'glyf' table is absent.
+
+ Note that removing overlaps invalidates the hinting. By default we drop hinting
+ from all glyphs whether or not overlaps are removed from a given one, as it would
+ look weird if only some glyphs are left (un)hinted.
+
+ Args:
+ font: input TTFont object, modified in place.
+ glyphNames: optional iterable of glyph names (str) to remove overlaps from.
+ By default, all glyphs in the font are processed.
+ removeHinting (bool): set to False to keep hinting for unmodified glyphs.
+ """
+ try:
+ glyfTable = font["glyf"]
+ except KeyError:
+ raise NotImplementedError("removeOverlaps currently only works with TTFs")
+
+ hmtxTable = font["hmtx"]
+ # wraps the underlying glyf Glyphs, takes care of interfacing with drawing pens
+ glyphSet = font.getGlyphSet()
+
+ if glyphNames is None:
+ glyphNames = font.getGlyphOrder()
+
+ # process all simple glyphs first, then composites with increasing component depth,
+ # so that by the time we test for component intersections the respective base glyphs
+ # have already been simplified
+ glyphNames = sorted(
+ glyphNames,
+ key=lambda name: (
+ glyfTable[name].getCompositeMaxpValues(glyfTable).maxComponentDepth
+ if glyfTable[name].isComposite()
+ else 0,
+ name,
+ ),
+ )
+ modified = set()
+ for glyphName in glyphNames:
+ if removeTTGlyphOverlaps(
+ glyphName, glyphSet, glyfTable, hmtxTable, removeHinting
+ ):
+ modified.add(glyphName)
+
+ log.debug("Removed overlaps for %s glyphs:\n%s", len(modified), " ".join(modified))
+
+
+def main(args=None):
+ import sys
+
+ if args is None:
+ args = sys.argv[1:]
+
+ if len(args) < 2:
+ print(
+ f"usage: fonttools ttLib.removeOverlaps INPUT.ttf OUTPUT.ttf [GLYPHS ...]"
+ )
+ sys.exit(1)
+
+ src = args[0]
+ dst = args[1]
+ glyphNames = args[2:] or None
+
+ with ttFont.TTFont(src) as f:
+ removeOverlaps(f, glyphNames)
+ f.save(dst)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/Lib/fontTools/ttLib/sfnt.py b/Lib/fontTools/ttLib/sfnt.py
index 1c11de72..d609dc51 100644
--- a/Lib/fontTools/ttLib/sfnt.py
+++ b/Lib/fontTools/ttLib/sfnt.py
@@ -12,8 +12,9 @@ classes, since whenever to number of tables changes or whenever
a table's length chages you need to rewrite the whole file anyway.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from io import BytesIO
+from types import SimpleNamespace
+from fontTools.misc.py23 import Tag
from fontTools.misc import sstruct
from fontTools.ttLib import TTLibError
import struct
@@ -42,7 +43,7 @@ class SFNTReader(object):
# return default object
return object.__new__(cls)
- def __init__(self, file, checkChecksums=1, fontNumber=-1):
+ def __init__(self, file, checkChecksums=0, fontNumber=-1):
self.file = file
self.checkChecksums = checkChecksums
@@ -123,29 +124,29 @@ class SFNTReader(object):
def close(self):
self.file.close()
- def __deepcopy__(self, memo):
- """Overrides the default deepcopy of SFNTReader object, to make it work
- in the case when TTFont is loaded with lazy=True, and thus reader holds a
- reference to a file object which is not pickleable.
- We work around it by manually copying the data into a in-memory stream.
- """
- from copy import deepcopy
-
- cls = self.__class__
- obj = cls.__new__(cls)
- for k, v in self.__dict__.items():
- if k == "file":
- pos = v.tell()
- v.seek(0)
- buf = BytesIO(v.read())
- v.seek(pos)
- buf.seek(pos)
- if hasattr(v, "name"):
- buf.name = v.name
- obj.file = buf
- else:
- obj.__dict__[k] = deepcopy(v, memo)
- return obj
+ # We define custom __getstate__ and __setstate__ to make SFNTReader pickle-able
+ # and deepcopy-able. When a TTFont is loaded as lazy=True, SFNTReader holds a
+ # reference to an external file object which is not pickleable. So in __getstate__
+ # we store the file name and current position, and in __setstate__ we reopen the
+ # same named file after unpickling.
+
+ def __getstate__(self):
+ if isinstance(self.file, BytesIO):
+ # BytesIO is already pickleable, return the state unmodified
+ return self.__dict__
+
+ # remove unpickleable file attribute, and only store its name and pos
+ state = self.__dict__.copy()
+ del state["file"]
+ state["_filename"] = self.file.name
+ state["_filepos"] = self.file.tell()
+ return state
+
+ def __setstate__(self, state):
+ if "file" not in state:
+ self.file = open(state.pop("_filename"), "rb")
+ self.file.seek(state.pop("_filepos"))
+ self.__dict__.update(state)
# default compression level for WOFF 1.0 tables and metadata
@@ -554,8 +555,7 @@ class WOFFFlavorData():
reader.file.seek(reader.metaOffset)
rawData = reader.file.read(reader.metaLength)
assert len(rawData) == reader.metaLength
- import zlib
- data = zlib.decompress(rawData)
+ data = self._decompress(rawData)
assert len(data) == reader.metaOrigLength
self.metaData = data
if reader.privLength:
@@ -564,6 +564,10 @@ class WOFFFlavorData():
assert len(data) == reader.privLength
self.privData = data
+ def _decompress(self, rawData):
+ import zlib
+ return zlib.decompress(rawData)
+
def calcChecksum(data):
"""Calculate the checksum for an arbitrary block of data.
diff --git a/Lib/fontTools/ttLib/standardGlyphOrder.py b/Lib/fontTools/ttLib/standardGlyphOrder.py
index 510773a6..1f980e45 100644
--- a/Lib/fontTools/ttLib/standardGlyphOrder.py
+++ b/Lib/fontTools/ttLib/standardGlyphOrder.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
#
# 'post' table formats 1.0 and 2.0 rely on this list of "standard"
# glyphs.
diff --git a/Lib/fontTools/ttLib/tables/B_A_S_E_.py b/Lib/fontTools/ttLib/tables/B_A_S_E_.py
index 14906b40..9551e2c6 100644
--- a/Lib/fontTools/ttLib/tables/B_A_S_E_.py
+++ b/Lib/fontTools/ttLib/tables/B_A_S_E_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py b/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py
index 685979a7..9197923d 100644
--- a/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py
+++ b/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py
@@ -1,7 +1,5 @@
# Since bitmap glyph metrics are shared between EBLC and EBDT
# this class gets its own python file.
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
import logging
diff --git a/Lib/fontTools/ttLib/tables/C_B_D_T_.py b/Lib/fontTools/ttLib/tables/C_B_D_T_.py
index ba02910d..11bb60b8 100644
--- a/Lib/fontTools/ttLib/tables/C_B_D_T_.py
+++ b/Lib/fontTools/ttLib/tables/C_B_D_T_.py
@@ -3,8 +3,7 @@
# Google Author(s): Matt Fontaine
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
from . import E_B_D_T_
from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat
diff --git a/Lib/fontTools/ttLib/tables/C_B_L_C_.py b/Lib/fontTools/ttLib/tables/C_B_L_C_.py
index 3d67dd0c..2f785710 100644
--- a/Lib/fontTools/ttLib/tables/C_B_L_C_.py
+++ b/Lib/fontTools/ttLib/tables/C_B_L_C_.py
@@ -2,8 +2,6 @@
#
# Google Author(s): Matt Fontaine
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from . import E_B_L_C_
class table_C_B_L_C_(E_B_L_C_.table_E_B_L_C_):
diff --git a/Lib/fontTools/ttLib/tables/C_F_F_.py b/Lib/fontTools/ttLib/tables/C_F_F_.py
index f175d5c7..d12b89d2 100644
--- a/Lib/fontTools/ttLib/tables/C_F_F_.py
+++ b/Lib/fontTools/ttLib/tables/C_F_F_.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from io import BytesIO
from fontTools import cffLib
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/C_F_F__2.py b/Lib/fontTools/ttLib/tables/C_F_F__2.py
index 7e30c8f4..6217ebba 100644
--- a/Lib/fontTools/ttLib/tables/C_F_F__2.py
+++ b/Lib/fontTools/ttLib/tables/C_F_F__2.py
@@ -1,6 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools import cffLib
+from io import BytesIO
from fontTools.ttLib.tables.C_F_F_ import table_C_F_F_
diff --git a/Lib/fontTools/ttLib/tables/C_O_L_R_.py b/Lib/fontTools/ttLib/tables/C_O_L_R_.py
index 441242ee..4004d417 100644
--- a/Lib/fontTools/ttLib/tables/C_O_L_R_.py
+++ b/Lib/fontTools/ttLib/tables/C_O_L_R_.py
@@ -2,11 +2,8 @@
#
# Google Author(s): Behdad Esfahbod
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
-import struct
class table_C_O_L_R_(DefaultTable.DefaultTable):
@@ -16,128 +13,132 @@ class table_C_O_L_R_(DefaultTable.DefaultTable):
ttFont['COLR'][<glyphName>] = <value> will set the color layers for any glyph.
"""
- def decompile(self, data, ttFont):
- self.getGlyphName = ttFont.getGlyphName # for use in get/set item functions, for access by GID
- self.version, numBaseGlyphRecords, offsetBaseGlyphRecord, offsetLayerRecord, numLayerRecords = struct.unpack(">HHLLH", data[:14])
- assert (self.version == 0), "Version of COLR table is higher than I know how to handle"
- glyphOrder = ttFont.getGlyphOrder()
- gids = []
- layerLists = []
- glyphPos = offsetBaseGlyphRecord
- for i in range(numBaseGlyphRecords):
- gid, firstLayerIndex, numLayers = struct.unpack(">HHH", data[glyphPos:glyphPos+6])
- glyphPos += 6
- gids.append(gid)
+ @staticmethod
+ def _decompileColorLayersV0(table):
+ if not table.LayerRecordArray:
+ return {}
+ colorLayerLists = {}
+ layerRecords = table.LayerRecordArray.LayerRecord
+ numLayerRecords = len(layerRecords)
+ for baseRec in table.BaseGlyphRecordArray.BaseGlyphRecord:
+ baseGlyph = baseRec.BaseGlyph
+ firstLayerIndex = baseRec.FirstLayerIndex
+ numLayers = baseRec.NumLayers
assert (firstLayerIndex + numLayers <= numLayerRecords)
- layerPos = offsetLayerRecord + firstLayerIndex * 4
layers = []
- for j in range(numLayers):
- layerGid, colorID = struct.unpack(">HH", data[layerPos:layerPos+4])
- try:
- layerName = glyphOrder[layerGid]
- except IndexError:
- layerName = self.getGlyphName(layerGid)
- layerPos += 4
- layers.append(LayerRecord(layerName, colorID))
- layerLists.append(layers)
-
- self.ColorLayers = colorLayerLists = {}
- try:
- names = [glyphOrder[gid] for gid in gids]
- except IndexError:
- getGlyphName = self.getGlyphName
- names = map(getGlyphName, gids)
-
- for name, layerList in zip(names, layerLists):
- colorLayerLists[name] = layerList
+ for i in range(firstLayerIndex, firstLayerIndex+numLayers):
+ layerRec = layerRecords[i]
+ layers.append(
+ LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex)
+ )
+ colorLayerLists[baseGlyph] = layers
+ return colorLayerLists
+
+ def _toOTTable(self, ttFont):
+ from . import otTables
+ from fontTools.colorLib.builder import populateCOLRv0
+
+ tableClass = getattr(otTables, self.tableTag)
+ table = tableClass()
+ table.Version = self.version
+
+ populateCOLRv0(
+ table,
+ {
+ baseGlyph: [(layer.name, layer.colorID) for layer in layers]
+ for baseGlyph, layers in self.ColorLayers.items()
+ },
+ glyphMap=ttFont.getReverseGlyphMap(rebuild=True),
+ )
+ return table
+
+ def decompile(self, data, ttFont):
+ from .otBase import OTTableReader
+ from . import otTables
+
+ # We use otData to decompile, but we adapt the decompiled otTables to the
+ # existing COLR v0 API for backward compatibility.
+ reader = OTTableReader(data, tableTag=self.tableTag)
+ tableClass = getattr(otTables, self.tableTag)
+ table = tableClass()
+ table.decompile(reader, ttFont)
+
+ self.version = table.Version
+ if self.version == 0:
+ self.ColorLayers = self._decompileColorLayersV0(table)
+ else:
+ # for new versions, keep the raw otTables around
+ self.table = table
def compile(self, ttFont):
- ordered = []
- ttFont.getReverseGlyphMap(rebuild=True)
- glyphNames = self.ColorLayers.keys()
- for glyphName in glyphNames:
- try:
- gid = ttFont.getGlyphID(glyphName)
- except:
- assert 0, "COLR table contains a glyph name not in ttFont.getGlyphNames(): " + str(glyphName)
- ordered.append([gid, glyphName, self.ColorLayers[glyphName]])
- ordered.sort()
-
- glyphMap = []
- layerMap = []
- for (gid, glyphName, layers) in ordered:
- glyphMap.append(struct.pack(">HHH", gid, len(layerMap), len(layers)))
- for layer in layers:
- layerMap.append(struct.pack(">HH", ttFont.getGlyphID(layer.name), layer.colorID))
-
- dataList = [struct.pack(">HHLLH", self.version, len(glyphMap), 14, 14+6*len(glyphMap), len(layerMap))]
- dataList.extend(glyphMap)
- dataList.extend(layerMap)
- data = bytesjoin(dataList)
- return data
+ from .otBase import OTTableWriter
+
+ if hasattr(self, "table"):
+ table = self.table
+ else:
+ table = self._toOTTable(ttFont)
+
+ writer = OTTableWriter(tableTag=self.tableTag)
+ table.compile(writer, ttFont)
+ return writer.getAllData()
def toXML(self, writer, ttFont):
- writer.simpletag("version", value=self.version)
- writer.newline()
- ordered = []
- glyphNames = self.ColorLayers.keys()
- for glyphName in glyphNames:
- try:
- gid = ttFont.getGlyphID(glyphName)
- except:
- assert 0, "COLR table contains a glyph name not in ttFont.getGlyphNames(): " + str(glyphName)
- ordered.append([gid, glyphName, self.ColorLayers[glyphName]])
- ordered.sort()
- for entry in ordered:
- writer.begintag("ColorGlyph", name=entry[1])
- writer.newline()
- for layer in entry[2]:
- layer.toXML(writer, ttFont)
- writer.endtag("ColorGlyph")
+ if hasattr(self, "table"):
+ self.table.toXML2(writer, ttFont)
+ else:
+ writer.simpletag("version", value=self.version)
writer.newline()
+ for baseGlyph in sorted(self.ColorLayers.keys(), key=ttFont.getGlyphID):
+ writer.begintag("ColorGlyph", name=baseGlyph)
+ writer.newline()
+ for layer in self.ColorLayers[baseGlyph]:
+ layer.toXML(writer, ttFont)
+ writer.endtag("ColorGlyph")
+ writer.newline()
def fromXML(self, name, attrs, content, ttFont):
- if not hasattr(self, "ColorLayers"):
- self.ColorLayers = {}
- self.getGlyphName = ttFont.getGlyphName # for use in get/set item functions, for access by GID
- if name == "ColorGlyph":
+ if name == "version": # old COLR v0 API
+ setattr(self, name, safeEval(attrs["value"]))
+ elif name == "ColorGlyph":
+ if not hasattr(self, "ColorLayers"):
+ self.ColorLayers = {}
glyphName = attrs["name"]
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
layers = []
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
layer = LayerRecord()
layer.fromXML(element[0], element[1], element[2], ttFont)
layers.append (layer)
- self[glyphName] = layers
- elif "value" in attrs:
- setattr(self, name, safeEval(attrs["value"]))
-
- def __getitem__(self, glyphSelector):
- if isinstance(glyphSelector, int):
- # its a gid, convert to glyph name
- glyphSelector = self.getGlyphName(glyphSelector)
-
- if glyphSelector not in self.ColorLayers:
- return None
-
- return self.ColorLayers[glyphSelector]
-
- def __setitem__(self, glyphSelector, value):
- if isinstance(glyphSelector, int):
- # its a gid, convert to glyph name
- glyphSelector = self.getGlyphName(glyphSelector)
-
- if value:
- self.ColorLayers[glyphSelector] = value
- elif glyphSelector in self.ColorLayers:
- del self.ColorLayers[glyphSelector]
-
- def __delitem__(self, glyphSelector):
- del self.ColorLayers[glyphSelector]
+ self.ColorLayers[glyphName] = layers
+ else: # new COLR v1 API
+ from . import otTables
+
+ if not hasattr(self, "table"):
+ tableClass = getattr(otTables, self.tableTag)
+ self.table = tableClass()
+ self.table.fromXML(name, attrs, content, ttFont)
+ self.table.populateDefaults()
+ self.version = self.table.Version
+
+ def __getitem__(self, glyphName):
+ if not isinstance(glyphName, str):
+ raise TypeError(f"expected str, found {type(glyphName).__name__}")
+ return self.ColorLayers[glyphName]
+
+ def __setitem__(self, glyphName, value):
+ if not isinstance(glyphName, str):
+ raise TypeError(f"expected str, found {type(glyphName).__name__}")
+ if value is not None:
+ self.ColorLayers[glyphName] = value
+ elif glyphName in self.ColorLayers:
+ del self.ColorLayers[glyphName]
+
+ def __delitem__(self, glyphName):
+ del self.ColorLayers[glyphName]
class LayerRecord(object):
@@ -152,8 +153,6 @@ class LayerRecord(object):
def fromXML(self, eltname, attrs, content, ttFont):
for (name, value) in attrs.items():
if name == "name":
- if isinstance(value, int):
- value = ttFont.getGlyphName(value)
setattr(self, name, value)
else:
setattr(self, name, safeEval(value))
diff --git a/Lib/fontTools/ttLib/tables/C_P_A_L_.py b/Lib/fontTools/ttLib/tables/C_P_A_L_.py
index f9d0c643..c095095e 100644
--- a/Lib/fontTools/ttLib/tables/C_P_A_L_.py
+++ b/Lib/fontTools/ttLib/tables/C_P_A_L_.py
@@ -2,8 +2,7 @@
#
# Google Author(s): Behdad Esfahbod
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import array
@@ -14,6 +13,9 @@ import sys
class table_C_P_A_L_(DefaultTable.DefaultTable):
+ NO_NAME_ID = 0xFFFF
+ DEFAULT_PALETTE_TYPE = 0
+
def __init__(self, tag=None):
DefaultTable.DefaultTable.__init__(self, tag)
self.palettes = []
@@ -46,24 +48,25 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
offsetToPaletteEntryLabelArray) = (
struct.unpack(">LLL", data[pos:pos+12]))
self.paletteTypes = self._decompileUInt32Array(
- data, offsetToPaletteTypeArray, numPalettes)
+ data, offsetToPaletteTypeArray, numPalettes,
+ default=self.DEFAULT_PALETTE_TYPE)
self.paletteLabels = self._decompileUInt16Array(
- data, offsetToPaletteLabelArray, numPalettes)
+ data, offsetToPaletteLabelArray, numPalettes, default=self.NO_NAME_ID)
self.paletteEntryLabels = self._decompileUInt16Array(
data, offsetToPaletteEntryLabelArray,
- self.numPaletteEntries)
+ self.numPaletteEntries, default=self.NO_NAME_ID)
- def _decompileUInt16Array(self, data, offset, numElements):
+ def _decompileUInt16Array(self, data, offset, numElements, default=0):
if offset == 0:
- return [0] * numElements
+ return [default] * numElements
result = array.array("H", data[offset : offset + 2 * numElements])
if sys.byteorder != "big": result.byteswap()
assert len(result) == numElements, result
return result.tolist()
- def _decompileUInt32Array(self, data, offset, numElements):
+ def _decompileUInt32Array(self, data, offset, numElements, default=0):
if offset == 0:
- return [0] * numElements
+ return [default] * numElements
result = array.array("I", data[offset : offset + 4 * numElements])
if sys.byteorder != "big": result.byteswap()
assert len(result) == numElements, result
@@ -137,7 +140,7 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
return result
def _compilePaletteLabels(self):
- if self.version == 0 or not any(self.paletteLabels):
+ if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteLabels):
return b''
assert len(self.paletteLabels) == len(self.palettes)
result = bytesjoin([struct.pack(">H", label)
@@ -146,7 +149,7 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
return result
def _compilePaletteEntryLabels(self):
- if self.version == 0 or not any(self.paletteEntryLabels):
+ if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteEntryLabels):
return b''
assert len(self.paletteEntryLabels) == self.numPaletteEntries
result = bytesjoin([struct.pack(">H", label)
@@ -166,15 +169,15 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
writer.newline()
for index, palette in enumerate(self.palettes):
attrs = {"index": index}
- paletteType = paletteTypes.get(index)
- paletteLabel = paletteLabels.get(index)
- if self.version > 0 and paletteLabel is not None:
+ paletteType = paletteTypes.get(index, self.DEFAULT_PALETTE_TYPE)
+ paletteLabel = paletteLabels.get(index, self.NO_NAME_ID)
+ if self.version > 0 and paletteLabel != self.NO_NAME_ID:
attrs["label"] = paletteLabel
- if self.version > 0 and paletteType is not None:
+ if self.version > 0 and paletteType != self.DEFAULT_PALETTE_TYPE:
attrs["type"] = paletteType
writer.begintag("palette", **attrs)
writer.newline()
- if (self.version > 0 and paletteLabel and
+ if (self.version > 0 and paletteLabel != self.NO_NAME_ID and
ttFont and "name" in ttFont):
name = ttFont["name"].getDebugName(paletteLabel)
if name is not None:
@@ -185,11 +188,11 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
color.toXML(writer, ttFont, cindex)
writer.endtag("palette")
writer.newline()
- if self.version > 0 and any(self.paletteEntryLabels):
+ if self.version > 0 and not all(l == self.NO_NAME_ID for l in self.paletteEntryLabels):
writer.begintag("paletteEntryLabels")
writer.newline()
for index, label in enumerate(self.paletteEntryLabels):
- if label:
+ if label != self.NO_NAME_ID:
writer.simpletag("label", index=index, value=label)
if (self.version > 0 and label and ttFont and "name" in ttFont):
name = ttFont["name"].getDebugName(label)
@@ -201,11 +204,11 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
def fromXML(self, name, attrs, content, ttFont):
if name == "palette":
- self.paletteLabels.append(int(attrs.get("label", "0")))
- self.paletteTypes.append(int(attrs.get("type", "0")))
+ self.paletteLabels.append(int(attrs.get("label", self.NO_NAME_ID)))
+ self.paletteTypes.append(int(attrs.get("type", self.DEFAULT_PALETTE_TYPE)))
palette = []
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
attrs = element[1]
color = Color.fromHex(attrs["value"])
@@ -214,7 +217,7 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
elif name == "paletteEntryLabels":
colorLabels = {}
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
elementName, elementAttr, _ = element
if elementName == "label":
@@ -222,13 +225,13 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
nameID = safeEval(elementAttr["value"])
colorLabels[labelIndex] = nameID
self.paletteEntryLabels = [
- colorLabels.get(i, 0)
+ colorLabels.get(i, self.NO_NAME_ID)
for i in range(self.numPaletteEntries)]
elif "value" in attrs:
value = safeEval(attrs["value"])
setattr(self, name, value)
if name == "numPaletteEntries":
- self.paletteEntryLabels = [0] * self.numPaletteEntries
+ self.paletteEntryLabels = [self.NO_NAME_ID] * self.numPaletteEntries
class Color(namedtuple("Color", "blue green red alpha")):
@@ -252,3 +255,7 @@ class Color(namedtuple("Color", "blue green red alpha")):
blue = int(value[4:6], 16)
alpha = int(value[6:8], 16) if len (value) >= 8 else 0xFF
return cls(red=red, green=green, blue=blue, alpha=alpha)
+
+ @classmethod
+ def fromRGBA(cls, red, green, blue, alpha):
+ return cls(red=red, green=green, blue=blue, alpha=alpha)
diff --git a/Lib/fontTools/ttLib/tables/D_S_I_G_.py b/Lib/fontTools/ttLib/tables/D_S_I_G_.py
index af802b95..1a520cab 100644
--- a/Lib/fontTools/ttLib/tables/D_S_I_G_.py
+++ b/Lib/fontTools/ttLib/tables/D_S_I_G_.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, strjoin, tobytes, tostr
from fontTools.misc.textTools import safeEval
from fontTools.misc import sstruct
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/D__e_b_g.py b/Lib/fontTools/ttLib/tables/D__e_b_g.py
new file mode 100644
index 00000000..ff64a9b5
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/D__e_b_g.py
@@ -0,0 +1,17 @@
+import json
+
+from . import DefaultTable
+
+
+class table_D__e_b_g(DefaultTable.DefaultTable):
+ def decompile(self, data, ttFont):
+ self.data = json.loads(data)
+
+ def compile(self, ttFont):
+ return json.dumps(self.data).encode("utf-8")
+
+ def toXML(self, writer, ttFont):
+ writer.writecdata(json.dumps(self.data))
+
+ def fromXML(self, name, attrs, content, ttFont):
+ self.data = json.loads(content)
diff --git a/Lib/fontTools/ttLib/tables/DefaultTable.py b/Lib/fontTools/ttLib/tables/DefaultTable.py
index 8ad36e16..c70480a3 100644
--- a/Lib/fontTools/ttLib/tables/DefaultTable.py
+++ b/Lib/fontTools/ttLib/tables/DefaultTable.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag
from fontTools.ttLib import getClassTag
class DefaultTable(object):
diff --git a/Lib/fontTools/ttLib/tables/E_B_D_T_.py b/Lib/fontTools/ttLib/tables/E_B_D_T_.py
index 3a316e5d..5d9e7244 100644
--- a/Lib/fontTools/ttLib/tables/E_B_D_T_.py
+++ b/Lib/fontTools/ttLib/tables/E_B_D_T_.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, strjoin
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval, readHex, hexStr, deHexStr
from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat
diff --git a/Lib/fontTools/ttLib/tables/E_B_L_C_.py b/Lib/fontTools/ttLib/tables/E_B_L_C_.py
index 8cf66007..94d40d96 100644
--- a/Lib/fontTools/ttLib/tables/E_B_L_C_.py
+++ b/Lib/fontTools/ttLib/tables/E_B_L_C_.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
from . import DefaultTable
from fontTools.misc.textTools import safeEval
@@ -155,7 +154,7 @@ class table_E_B_L_C_(DefaultTable.DefaultTable):
# (2) Build each bitmapSizeTable.
# (3) Consolidate all the data into the main dataList in the correct order.
- for curStrike in self.strikes:
+ for _ in self.strikes:
dataSize += sstruct.calcsize(bitmapSizeTableFormatPart1)
dataSize += len(('hori', 'vert')) * sstruct.calcsize(sbitLineMetricsFormat)
dataSize += sstruct.calcsize(bitmapSizeTableFormatPart2)
@@ -483,7 +482,7 @@ def _createOffsetArrayIndexSubTableMixin(formatStringForDataType):
dataList = [EblcIndexSubTable.compile(self, ttFont)]
dataList += [struct.pack(dataFormat, offsetValue) for offsetValue in offsetArray]
# Take care of any padding issues. Only occurs in format 3.
- if offsetDataSize * len(dataList) % 4 != 0:
+ if offsetDataSize * len(offsetArray) % 4 != 0:
dataList.append(struct.pack(dataFormat, 0))
return bytesjoin(dataList)
diff --git a/Lib/fontTools/ttLib/tables/F_F_T_M_.py b/Lib/fontTools/ttLib/tables/F_F_T_M_.py
index 3d110bdb..2376f2db 100644
--- a/Lib/fontTools/ttLib/tables/F_F_T_M_.py
+++ b/Lib/fontTools/ttLib/tables/F_F_T_M_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from fontTools.misc.timeTools import timestampFromString, timestampToString
diff --git a/Lib/fontTools/ttLib/tables/F__e_a_t.py b/Lib/fontTools/ttLib/tables/F__e_a_t.py
index ec497f22..7e510614 100644
--- a/Lib/fontTools/ttLib/tables/F__e_a_t.py
+++ b/Lib/fontTools/ttLib/tables/F__e_a_t.py
@@ -1,8 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
+from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
-from .otBase import BaseTTXConverter
from . import DefaultTable
from . import grUtils
import struct
@@ -20,6 +18,7 @@ class table_F__e_a_t(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
(_, data) = sstruct.unpack2(Feat_hdr_format, data, self)
+ self.version = float(floatToFixedToStr(self.version, precisionBits=16))
numFeats, = struct.unpack('>H', data[:2])
data = data[8:]
allfeats = []
diff --git a/Lib/fontTools/ttLib/tables/G_D_E_F_.py b/Lib/fontTools/ttLib/tables/G_D_E_F_.py
index 08faf627..d4a57414 100644
--- a/Lib/fontTools/ttLib/tables/G_D_E_F_.py
+++ b/Lib/fontTools/ttLib/tables/G_D_E_F_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/G_M_A_P_.py b/Lib/fontTools/ttLib/tables/G_M_A_P_.py
index afa8c4dc..5b30dcfe 100644
--- a/Lib/fontTools/ttLib/tables/G_M_A_P_.py
+++ b/Lib/fontTools/ttLib/tables/G_M_A_P_.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes, tostr
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from . import DefaultTable
@@ -116,7 +115,7 @@ class table_G_M_A_P_(DefaultTable.DefaultTable):
gmapRecord = GMAPRecord()
self.gmapRecords.append(gmapRecord)
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
gmapRecord.fromXML(name, attrs, content, ttFont)
diff --git a/Lib/fontTools/ttLib/tables/G_P_K_G_.py b/Lib/fontTools/ttLib/tables/G_P_K_G_.py
index b835f4af..7598a62a 100644
--- a/Lib/fontTools/ttLib/tables/G_P_K_G_.py
+++ b/Lib/fontTools/ttLib/tables/G_P_K_G_.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval, readHex
from . import DefaultTable
@@ -24,7 +23,7 @@ class table_G_P_K_G_(DefaultTable.DefaultTable):
GMAPoffsets = array.array("I")
endPos = (self.numGMAPs+1) * 4
- GMAPoffsets.fromstring(newData[:endPos])
+ GMAPoffsets.frombytes(newData[:endPos])
if sys.byteorder != "big": GMAPoffsets.byteswap()
self.GMAPs = []
for i in range(self.numGMAPs):
@@ -34,7 +33,7 @@ class table_G_P_K_G_(DefaultTable.DefaultTable):
pos = endPos
endPos = pos + (self.numGlyplets + 1)*4
glyphletOffsets = array.array("I")
- glyphletOffsets.fromstring(newData[pos:endPos])
+ glyphletOffsets.frombytes(newData[pos:endPos])
if sys.byteorder != "big": glyphletOffsets.byteswap()
self.glyphlets = []
for i in range(self.numGlyplets):
@@ -57,7 +56,7 @@ class table_G_P_K_G_(DefaultTable.DefaultTable):
GMAPoffsets[i] = pos
gmapArray = array.array("I", GMAPoffsets)
if sys.byteorder != "big": gmapArray.byteswap()
- dataList.append(gmapArray.tostring())
+ dataList.append(gmapArray.tobytes())
glyphletOffsets[0] = pos
for i in range(1, self.numGlyplets +1):
@@ -65,7 +64,7 @@ class table_G_P_K_G_(DefaultTable.DefaultTable):
glyphletOffsets[i] = pos
glyphletArray = array.array("I", glyphletOffsets)
if sys.byteorder != "big": glyphletArray.byteswap()
- dataList.append(glyphletArray.tostring())
+ dataList.append(glyphletArray.tobytes())
dataList += self.GMAPs
dataList += self.glyphlets
data = bytesjoin(dataList)
@@ -107,7 +106,7 @@ class table_G_P_K_G_(DefaultTable.DefaultTable):
if not hasattr(self, "GMAPs"):
self.GMAPs = []
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
itemName, itemAttrs, itemContent = element
if itemName == "hexdata":
@@ -116,7 +115,7 @@ class table_G_P_K_G_(DefaultTable.DefaultTable):
if not hasattr(self, "glyphlets"):
self.glyphlets = []
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
itemName, itemAttrs, itemContent = element
if itemName == "hexdata":
diff --git a/Lib/fontTools/ttLib/tables/G_P_O_S_.py b/Lib/fontTools/ttLib/tables/G_P_O_S_.py
index 1c36061c..013c8209 100644
--- a/Lib/fontTools/ttLib/tables/G_P_O_S_.py
+++ b/Lib/fontTools/ttLib/tables/G_P_O_S_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/G_S_U_B_.py b/Lib/fontTools/ttLib/tables/G_S_U_B_.py
index d23e8ba1..44036490 100644
--- a/Lib/fontTools/ttLib/tables/G_S_U_B_.py
+++ b/Lib/fontTools/ttLib/tables/G_S_U_B_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/G__l_a_t.py b/Lib/fontTools/ttLib/tables/G__l_a_t.py
index b7e8281b..a4e8e38f 100644
--- a/Lib/fontTools/ttLib/tables/G__l_a_t.py
+++ b/Lib/fontTools/ttLib/tables/G__l_a_t.py
@@ -1,12 +1,11 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
+from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
-from itertools import *
+# from itertools import *
from functools import partial
from . import DefaultTable
from . import grUtils
-import struct, operator, warnings
+import struct
Glat_format_0 = """
@@ -69,6 +68,7 @@ class table_G__l_a_t(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
sstruct.unpack2(Glat_format_0, data, self)
+ self.version = float(floatToFixedToStr(self.version, precisionBits=16))
if self.version <= 1.9:
decoder = partial(self.decompileAttributes12,fmt=Glat_format_1_entry)
elif self.version <= 2.9:
diff --git a/Lib/fontTools/ttLib/tables/G__l_o_c.py b/Lib/fontTools/ttLib/tables/G__l_o_c.py
index 188637d8..fa114a31 100644
--- a/Lib/fontTools/ttLib/tables/G__l_o_c.py
+++ b/Lib/fontTools/ttLib/tables/G__l_o_c.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from . import DefaultTable
@@ -31,11 +29,11 @@ class table_G__l_o_c(DefaultTable.DefaultTable):
flags = self.flags
del self.flags
self.locations = array.array('I' if flags & 1 else 'H')
- self.locations.fromstring(data[:len(data) - self.numAttribs * (flags & 2)])
+ self.locations.frombytes(data[:len(data) - self.numAttribs * (flags & 2)])
if sys.byteorder != "big": self.locations.byteswap()
self.attribIds = array.array('H')
if flags & 2:
- self.attribIds.fromstring(data[-self.numAttribs * 2:])
+ self.attribIds.frombytes(data[-self.numAttribs * 2:])
if sys.byteorder != "big": self.attribIds.byteswap()
def compile(self, ttFont):
@@ -43,11 +41,11 @@ class table_G__l_o_c(DefaultTable.DefaultTable):
flags=(bool(self.attribIds) << 1) + (self.locations.typecode == 'I'),
numAttribs=self.numAttribs))
if sys.byteorder != "big": self.locations.byteswap()
- data += self.locations.tostring()
+ data += self.locations.tobytes()
if sys.byteorder != "big": self.locations.byteswap()
if self.attribIds:
if sys.byteorder != "big": self.attribIds.byteswap()
- data += self.attribIds.tostring()
+ data += self.attribIds.tobytes()
if sys.byteorder != "big": self.attribIds.byteswap()
return data
diff --git a/Lib/fontTools/ttLib/tables/H_V_A_R_.py b/Lib/fontTools/ttLib/tables/H_V_A_R_.py
index efab4e72..56992ad0 100644
--- a/Lib/fontTools/ttLib/tables/H_V_A_R_.py
+++ b/Lib/fontTools/ttLib/tables/H_V_A_R_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/J_S_T_F_.py b/Lib/fontTools/ttLib/tables/J_S_T_F_.py
index dffd08b0..ddf54055 100644
--- a/Lib/fontTools/ttLib/tables/J_S_T_F_.py
+++ b/Lib/fontTools/ttLib/tables/J_S_T_F_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/L_T_S_H_.py b/Lib/fontTools/ttLib/tables/L_T_S_H_.py
index dd0f1954..94c2c22a 100644
--- a/Lib/fontTools/ttLib/tables/L_T_S_H_.py
+++ b/Lib/fontTools/ttLib/tables/L_T_S_H_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import struct
@@ -19,7 +17,7 @@ class table_L_T_S_H_(DefaultTable.DefaultTable):
# ouch: the assertion is not true in Chicago!
#assert numGlyphs == ttFont['maxp'].numGlyphs
yPels = array.array("B")
- yPels.fromstring(data)
+ yPels.frombytes(data)
self.yPels = {}
for i in range(numGlyphs):
self.yPels[ttFont.getGlyphName(i)] = yPels[i]
@@ -34,7 +32,7 @@ class table_L_T_S_H_(DefaultTable.DefaultTable):
for name in names:
yPels[ttFont.getGlyphID(name)] = self.yPels[name]
yPels = array.array("B", yPels)
- return struct.pack(">HH", version, numGlyphs) + yPels.tostring()
+ return struct.pack(">HH", version, numGlyphs) + yPels.tobytes()
def toXML(self, writer, ttFont):
names = sorted(self.yPels.keys())
diff --git a/Lib/fontTools/ttLib/tables/M_A_T_H_.py b/Lib/fontTools/ttLib/tables/M_A_T_H_.py
index 8c329ba5..d894c082 100644
--- a/Lib/fontTools/ttLib/tables/M_A_T_H_.py
+++ b/Lib/fontTools/ttLib/tables/M_A_T_H_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/M_E_T_A_.py b/Lib/fontTools/ttLib/tables/M_E_T_A_.py
index 3eb6550d..d4f6bc8c 100644
--- a/Lib/fontTools/ttLib/tables/M_E_T_A_.py
+++ b/Lib/fontTools/ttLib/tables/M_E_T_A_.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import byteord
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from . import DefaultTable
@@ -174,7 +173,7 @@ class table_M_E_T_A_(DefaultTable.DefaultTable):
glyphRec = GlyphRecord()
self.glyphRecords.append(glyphRec)
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
glyphRec.fromXML(name, attrs, content, ttFont)
@@ -208,7 +207,7 @@ class GlyphRecord(object):
stringRec = StringRecord()
self.stringRecs.append(stringRec)
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
stringRec.fromXML(name, attrs, content, ttFont)
stringRec.stringLen = len(stringRec.string)
@@ -230,7 +229,7 @@ class GlyphRecord(object):
# XXX The following two functions are really broken around UTF-8 vs Unicode
def mapXMLToUTF8(string):
- uString = unicode()
+ uString = str()
strLen = len(string)
i = 0
while i < strLen:
@@ -246,9 +245,9 @@ def mapXMLToUTF8(string):
i = i+1
valStr = string[j:i]
- uString = uString + unichr(eval('0x' + valStr))
+ uString = uString + chr(eval('0x' + valStr))
else:
- uString = uString + unichr(byteord(string[i]))
+ uString = uString + chr(byteord(string[i]))
i = i +1
return uString.encode('utf_8')
@@ -282,7 +281,7 @@ class StringRecord(object):
def fromXML(self, name, attrs, content, ttFont):
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
value = attrs["value"]
diff --git a/Lib/fontTools/ttLib/tables/M_V_A_R_.py b/Lib/fontTools/ttLib/tables/M_V_A_R_.py
index 8659ae83..34ab20f7 100644
--- a/Lib/fontTools/ttLib/tables/M_V_A_R_.py
+++ b/Lib/fontTools/ttLib/tables/M_V_A_R_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/O_S_2f_2.py b/Lib/fontTools/ttLib/tables/O_S_2f_2.py
index 107d8590..a5765224 100644
--- a/Lib/fontTools/ttLib/tables/O_S_2f_2.py
+++ b/Lib/fontTools/ttLib/tables/O_S_2f_2.py
@@ -1,8 +1,7 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval, num2binary, binary2num
from fontTools.ttLib.tables import DefaultTable
+import bisect
import logging
@@ -476,22 +475,19 @@ OS2_UNICODE_RANGES = (
)
-_unicodeRangeSets = []
+_unicodeStarts = []
+_unicodeValues = [None]
-def _getUnicodeRangeSets():
- # build the sets of codepoints for each unicode range bit, and cache result
- if not _unicodeRangeSets:
- for bit, blocks in enumerate(OS2_UNICODE_RANGES):
- rangeset = set()
- for _, (start, stop) in blocks:
- rangeset.update(set(range(start, stop+1)))
- if bit == 57:
- # The spec says that bit 57 ("Non Plane 0") implies that there's
- # at least one codepoint beyond the BMP; so I also include all
- # the non-BMP codepoints here
- rangeset.update(set(range(0x10000, 0x110000)))
- _unicodeRangeSets.append(rangeset)
- return _unicodeRangeSets
+def _getUnicodeRanges():
+ # build the ranges of codepoints for each unicode range bit, and cache result
+ if not _unicodeStarts:
+ unicodeRanges = [
+ (start, (stop, bit)) for bit, blocks in enumerate(OS2_UNICODE_RANGES)
+ for _, (start, stop) in blocks]
+ for start, (stop, bit) in sorted(unicodeRanges):
+ _unicodeStarts.append(start)
+ _unicodeValues.append((stop, bit))
+ return _unicodeStarts, _unicodeValues
def intersectUnicodeRanges(unicodes, inverse=False):
@@ -505,15 +501,22 @@ def intersectUnicodeRanges(unicodes, inverse=False):
>>> intersectUnicodeRanges([0x0410, 0x1F000]) == {9, 57, 122}
True
>>> intersectUnicodeRanges([0x0410, 0x1F000], inverse=True) == (
- ... set(range(123)) - {9, 57, 122})
+ ... set(range(len(OS2_UNICODE_RANGES))) - {9, 57, 122})
True
"""
unicodes = set(unicodes)
- uniranges = _getUnicodeRangeSets()
- bits = set([
- bit for bit, unirange in enumerate(uniranges)
- if not unirange.isdisjoint(unicodes) ^ inverse])
- return bits
+ unicodestarts, unicodevalues = _getUnicodeRanges()
+ bits = set()
+ for code in unicodes:
+ stop, bit = unicodevalues[bisect.bisect(unicodestarts, code)]
+ if code <= stop:
+ bits.add(bit)
+ # The spec says that bit 57 ("Non Plane 0") implies that there's
+ # at least one codepoint beyond the BMP; so I also include all
+ # the non-BMP codepoints here
+ if any(0x10000 <= code < 0x110000 for code in unicodes):
+ bits.add(57)
+ return set(range(len(OS2_UNICODE_RANGES))) - bits if inverse else bits
if __name__ == "__main__":
diff --git a/Lib/fontTools/ttLib/tables/S_I_N_G_.py b/Lib/fontTools/ttLib/tables/S_I_N_G_.py
index 413ae484..dd9b63c4 100644
--- a/Lib/fontTools/ttLib/tables/S_I_N_G_.py
+++ b/Lib/fontTools/ttLib/tables/S_I_N_G_.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, tobytes, tostr
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/S_T_A_T_.py b/Lib/fontTools/ttLib/tables/S_T_A_T_.py
index 1e044cf1..1769de91 100644
--- a/Lib/fontTools/ttLib/tables/S_T_A_T_.py
+++ b/Lib/fontTools/ttLib/tables/S_T_A_T_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/S_V_G_.py b/Lib/fontTools/ttLib/tables/S_V_G_.py
index b0611531..135f2718 100644
--- a/Lib/fontTools/ttLib/tables/S_V_G_.py
+++ b/Lib/fontTools/ttLib/tables/S_V_G_.py
@@ -1,13 +1,12 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, strjoin, tobytes, tostr
from fontTools.misc import sstruct
from . import DefaultTable
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
+from io import BytesIO
import struct
-import re
import logging
diff --git a/Lib/fontTools/ttLib/tables/S__i_l_f.py b/Lib/fontTools/ttLib/tables/S__i_l_f.py
index 00d5f616..95880b07 100644
--- a/Lib/fontTools/ttLib/tables/S__i_l_f.py
+++ b/Lib/fontTools/ttLib/tables/S__i_l_f.py
@@ -1,13 +1,13 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import byteord
from fontTools.misc import sstruct
+from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
-from itertools import *
+# from itertools import *
from . import DefaultTable
from . import grUtils
from array import array
from functools import reduce
-import struct, operator, warnings, re, sys
+import struct, re, sys
Silf_hdr_format = '''
>
@@ -313,6 +313,7 @@ class table_S__i_l_f(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
sstruct.unpack2(Silf_hdr_format, data, self)
+ self.version = float(floatToFixedToStr(self.version, precisionBits=16))
if self.version >= 5.0:
(data, self.scheme) = grUtils.decompress(data)
sstruct.unpack2(Silf_hdr_format_3, data, self)
@@ -391,6 +392,7 @@ class Silf(object):
def decompile(self, data, ttFont, version=2.0):
if version >= 3.0 :
_, data = sstruct.unpack2(Silf_part1_format_v3, data, self)
+ self.ruleVersion = float(floatToFixedToStr(self.ruleVersion, precisionBits=16))
_, data = sstruct.unpack2(Silf_part1_format, data, self)
for jlevel in range(self.numJLevels):
j, data = sstruct.unpack2(Silf_justify_format, data, _Object())
@@ -401,7 +403,7 @@ class Silf(object):
data = data[self.numCritFeatures * 2 + 1:]
(numScriptTag,) = struct.unpack_from('B', data)
if numScriptTag:
- self.scriptTags = [struct.unpack("4s", data[x:x+4])[0] for x in range(1, 1 + 4 * numScriptTag, 4)]
+ self.scriptTags = [struct.unpack("4s", data[x:x+4])[0].decode("ascii") for x in range(1, 1 + 4 * numScriptTag, 4)]
data = data[1 + 4 * numScriptTag:]
(self.lbGID,) = struct.unpack('>H', data[:2])
if self.numPasses:
@@ -447,8 +449,8 @@ class Silf(object):
data += struct.pack((">%dH" % self.numCritFeaturs), *self.critFeatures)
data += struct.pack("BB", 0, len(self.scriptTags))
if len(self.scriptTags):
- tdata = [struct.pack("4s", x) for x in self.scriptTags]
- data += "".join(tdata)
+ tdata = [struct.pack("4s", x.encode("ascii")) for x in self.scriptTags]
+ data += b"".join(tdata)
data += struct.pack(">H", self.lbGID)
self.passOffset = len(data)
@@ -746,7 +748,7 @@ class Pass(object):
transes = []
for t in self.stateTrans:
if sys.byteorder != "big": t.byteswap()
- transes.append(t.tostring())
+ transes.append(t.tobytes())
if sys.byteorder != "big": t.byteswap()
if not len(transes):
self.startStates = [0]
diff --git a/Lib/fontTools/ttLib/tables/S__i_l_l.py b/Lib/fontTools/ttLib/tables/S__i_l_l.py
index bf9d83f5..5ab9ee34 100644
--- a/Lib/fontTools/ttLib/tables/S__i_l_l.py
+++ b/Lib/fontTools/ttLib/tables/S__i_l_l.py
@@ -1,6 +1,5 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
+from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
from . import DefaultTable
from . import grUtils
@@ -19,6 +18,7 @@ class table_S__i_l_l(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
(_, data) = sstruct.unpack2(Sill_hdr, data, self)
+ self.version = float(floatToFixedToStr(self.version, precisionBits=16))
numLangs, = struct.unpack('>H', data[:2])
data = data[8:]
maxsetting = 0
@@ -44,12 +44,13 @@ class table_S__i_l_l(DefaultTable.DefaultTable):
def compile(self, ttFont):
ldat = b""
fdat = b""
- offset = 0
+ offset = len(self.langs)
for c, inf in sorted(self.langs.items()):
- ldat += struct.pack(">4sHH", c.encode('utf8'), len(inf), 8 * (offset + len(self.langs) + 1))
+ ldat += struct.pack(">4sHH", c.encode('utf8'), len(inf), 8 * offset + 20)
for fid, val in inf:
fdat += struct.pack(">LHH", fid, val, 0)
offset += len(inf)
+ ldat += struct.pack(">LHH", 0x80808080, 0, 8 * offset + 20)
return sstruct.pack(Sill_hdr, self) + grUtils.bininfo(len(self.langs)) + \
ldat + fdat
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_B_.py b/Lib/fontTools/ttLib/tables/T_S_I_B_.py
index 7d47e3a2..25d43104 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_B_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_B_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .T_S_I_V_ import table_T_S_I_V_
class table_T_S_I_B_(table_T_S_I_V_):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_C_.py b/Lib/fontTools/ttLib/tables/T_S_I_C_.py
index b4684a46..573b3f9c 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_C_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_C_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_D_.py b/Lib/fontTools/ttLib/tables/T_S_I_D_.py
index f989c9ed..310eb174 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_D_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_D_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .T_S_I_V_ import table_T_S_I_V_
class table_T_S_I_D_(table_T_S_I_V_):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_J_.py b/Lib/fontTools/ttLib/tables/T_S_I_J_.py
index ca538ba9..c1a46ba6 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_J_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_J_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .T_S_I_V_ import table_T_S_I_V_
class table_T_S_I_J_(table_T_S_I_V_):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_P_.py b/Lib/fontTools/ttLib/tables/T_S_I_P_.py
index 062d3327..778974c8 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_P_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_P_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .T_S_I_V_ import table_T_S_I_V_
class table_T_S_I_P_(table_T_S_I_V_):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_S_.py b/Lib/fontTools/ttLib/tables/T_S_I_S_.py
index 68e22ced..61c9f76f 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_S_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_S_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .T_S_I_V_ import table_T_S_I_V_
class table_T_S_I_S_(table_T_S_I_V_):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_V_.py b/Lib/fontTools/ttLib/tables/T_S_I_V_.py
index 46b2182e..80214452 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_V_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_V_.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import strjoin, tobytes, tostr
from . import asciiTable
class table_T_S_I_V_(asciiTable.asciiTable):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__0.py b/Lib/fontTools/ttLib/tables/T_S_I__0.py
index 81808eea..b187f425 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__0.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__0.py
@@ -5,8 +5,6 @@ TSI0 is the index table containing the lengths and offsets for the glyph
programs and 'extra' programs ('fpgm', 'prep', and 'cvt') that are contained
in the TSI1 table.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from . import DefaultTable
import struct
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__1.py b/Lib/fontTools/ttLib/tables/T_S_I__1.py
index 5ef944af..9ae7acd6 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__1.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__1.py
@@ -4,8 +4,7 @@ tool to store its hinting source data.
TSI1 contains the text of the glyph programs in the form of low-level assembly
code, as well as the 'extra' programs 'fpgm', 'ppgm' (i.e. 'prep'), and 'cvt'.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import strjoin, tobytes, tostr
from . import DefaultTable
from fontTools.misc.loggingTools import LogMixin
@@ -69,7 +68,7 @@ class table_T_S_I__1(LogMixin, DefaultTable.DefaultTable):
"%r textLength (%d) must not be > 32768" % (name, textLength))
text = data[textOffset:textOffset+textLength]
assert len(text) == textLength
- text = tounicode(text, encoding='utf-8')
+ text = tostr(text, encoding='utf-8')
if text:
programs[name] = text
if isExtra:
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__2.py b/Lib/fontTools/ttLib/tables/T_S_I__2.py
index 7d41ee88..036c9815 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__2.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__2.py
@@ -5,8 +5,6 @@ TSI2 is the index table containing the lengths and offsets for the glyph
programs that are contained in the TSI3 table. It uses the same format as
the TSI0 table.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools import ttLib
superclass = ttLib.getTableClass("TSI0")
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__3.py b/Lib/fontTools/ttLib/tables/T_S_I__3.py
index ee95d06f..a2490142 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__3.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__3.py
@@ -3,8 +3,6 @@ tool to store its hinting source data.
TSI3 contains the text of the glyph programs in the form of 'VTTTalk' code.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools import ttLib
superclass = ttLib.getTableClass("TSI1")
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__5.py b/Lib/fontTools/ttLib/tables/T_S_I__5.py
index 61b76044..7be09f9a 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__5.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__5.py
@@ -3,8 +3,6 @@ tool to store its hinting source data.
TSI5 contains the VTT character groups.
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import sys
@@ -17,7 +15,7 @@ class table_T_S_I__5(DefaultTable.DefaultTable):
numGlyphs = ttFont['maxp'].numGlyphs
assert len(data) == 2 * numGlyphs
a = array.array("H")
- a.fromstring(data)
+ a.frombytes(data)
if sys.byteorder != "big": a.byteswap()
self.glyphGrouping = {}
for i in range(numGlyphs):
@@ -29,7 +27,7 @@ class table_T_S_I__5(DefaultTable.DefaultTable):
for i in range(len(glyphNames)):
a.append(self.glyphGrouping.get(glyphNames[i], 0))
if sys.byteorder != "big": a.byteswap()
- return a.tostring()
+ return a.tobytes()
def toXML(self, writer, ttFont):
names = sorted(self.glyphGrouping.keys())
diff --git a/Lib/fontTools/ttLib/tables/T_T_F_A_.py b/Lib/fontTools/ttLib/tables/T_T_F_A_.py
index 8ff8d1b3..8446dfc5 100644
--- a/Lib/fontTools/ttLib/tables/T_T_F_A_.py
+++ b/Lib/fontTools/ttLib/tables/T_T_F_A_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from . import asciiTable
class table_T_T_F_A_(asciiTable.asciiTable):
diff --git a/Lib/fontTools/ttLib/tables/TupleVariation.py b/Lib/fontTools/ttLib/tables/TupleVariation.py
index fb2131b2..a63fb6c6 100644
--- a/Lib/fontTools/ttLib/tables/TupleVariation.py
+++ b/Lib/fontTools/ttLib/tables/TupleVariation.py
@@ -1,6 +1,11 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import fixedToFloat, floatToFixed, otRound
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin
+from fontTools.misc.fixedTools import (
+ fixedToFloat as fi2fl,
+ floatToFixed as fl2fi,
+ floatToFixedToStr as fl2str,
+ strToFixedToFloat as str2fl,
+ otRound,
+)
from fontTools.misc.textTools import safeEval
import array
import io
@@ -63,17 +68,17 @@ class TupleVariation(object):
for axis in axisTags:
value = self.axes.get(axis)
if value is not None:
- minValue, value, maxValue = (float(v) for v in value)
+ minValue, value, maxValue = value
defaultMinValue = min(value, 0.0) # -0.3 --> -0.3; 0.7 --> 0.0
defaultMaxValue = max(value, 0.0) # -0.3 --> 0.0; 0.7 --> 0.7
if minValue == defaultMinValue and maxValue == defaultMaxValue:
- writer.simpletag("coord", axis=axis, value=value)
+ writer.simpletag("coord", axis=axis, value=fl2str(value, 14))
else:
attrs = [
("axis", axis),
- ("min", minValue),
- ("value", value),
- ("max", maxValue),
+ ("min", fl2str(minValue, 14)),
+ ("value", fl2str(value, 14)),
+ ("max", fl2str(maxValue, 14)),
]
writer.simpletag("coord", attrs)
writer.newline()
@@ -101,11 +106,11 @@ class TupleVariation(object):
def fromXML(self, name, attrs, _content):
if name == "coord":
axis = attrs["axis"]
- value = float(attrs["value"])
+ value = str2fl(attrs["value"], 14)
defaultMinValue = min(value, 0.0) # -0.3 --> -0.3; 0.7 --> 0.0
defaultMaxValue = max(value, 0.0) # -0.3 --> 0.0; 0.7 --> 0.7
- minValue = float(attrs.get("min", defaultMinValue))
- maxValue = float(attrs.get("max", defaultMaxValue))
+ minValue = str2fl(attrs.get("min", defaultMinValue), 14)
+ maxValue = str2fl(attrs.get("max", defaultMaxValue), 14)
self.axes[axis] = (minValue, value, maxValue)
elif name == "delta":
if "pt" in attrs:
@@ -156,7 +161,7 @@ class TupleVariation(object):
result = []
for axis in axisTags:
_minValue, value, _maxValue = self.axes.get(axis, (0.0, 0.0, 0.0))
- result.append(struct.pack(">h", floatToFixed(value, 14)))
+ result.append(struct.pack(">h", fl2fi(value, 14)))
return bytesjoin(result)
def compileIntermediateCoord(self, axisTags):
@@ -174,8 +179,8 @@ class TupleVariation(object):
maxCoords = []
for axis in axisTags:
minValue, value, maxValue = self.axes.get(axis, (0.0, 0.0, 0.0))
- minCoords.append(struct.pack(">h", floatToFixed(minValue, 14)))
- maxCoords.append(struct.pack(">h", floatToFixed(maxValue, 14)))
+ minCoords.append(struct.pack(">h", fl2fi(minValue, 14)))
+ maxCoords.append(struct.pack(">h", fl2fi(maxValue, 14)))
return bytesjoin(minCoords + maxCoords)
@staticmethod
@@ -183,7 +188,7 @@ class TupleVariation(object):
coord = {}
pos = offset
for axis in axisTags:
- coord[axis] = fixedToFloat(struct.unpack(">h", data[pos:pos+2])[0], 14)
+ coord[axis] = fi2fl(struct.unpack(">h", data[pos:pos+2])[0], 14)
pos += 2
return coord, pos
@@ -270,7 +275,7 @@ class TupleVariation(object):
else:
points = array.array("B")
pointsSize = numPointsInRun
- points.fromstring(data[pos:pos+pointsSize])
+ points.frombytes(data[pos:pos+pointsSize])
if sys.byteorder != "big": points.byteswap()
assert len(points) == numPointsInRun
@@ -427,7 +432,7 @@ class TupleVariation(object):
else:
deltas = array.array("b")
deltasSize = numDeltasInRun
- deltas.fromstring(data[pos:pos+deltasSize])
+ deltas.frombytes(data[pos:pos+deltasSize])
if sys.byteorder != "big": deltas.byteswap()
assert len(deltas) == numDeltasInRun
pos += deltasSize
diff --git a/Lib/fontTools/ttLib/tables/V_D_M_X_.py b/Lib/fontTools/ttLib/tables/V_D_M_X_.py
index abca3bf1..ba8593f1 100644
--- a/Lib/fontTools/ttLib/tables/V_D_M_X_.py
+++ b/Lib/fontTools/ttLib/tables/V_D_M_X_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from . import DefaultTable
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
diff --git a/Lib/fontTools/ttLib/tables/V_O_R_G_.py b/Lib/fontTools/ttLib/tables/V_O_R_G_.py
index 1b752940..0b7fe959 100644
--- a/Lib/fontTools/ttLib/tables/V_O_R_G_.py
+++ b/Lib/fontTools/ttLib/tables/V_O_R_G_.py
@@ -1,8 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc.textTools import safeEval
from . import DefaultTable
-import operator
import struct
@@ -85,7 +83,7 @@ class table_V_O_R_G_(DefaultTable.DefaultTable):
if name == "VOriginRecord":
vOriginRec = VOriginRecord()
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
vOriginRec.fromXML(name, attrs, content, ttFont)
diff --git a/Lib/fontTools/ttLib/tables/V_V_A_R_.py b/Lib/fontTools/ttLib/tables/V_V_A_R_.py
index 530f0e3a..88f30552 100644
--- a/Lib/fontTools/ttLib/tables/V_V_A_R_.py
+++ b/Lib/fontTools/ttLib/tables/V_V_A_R_.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/__init__.py b/Lib/fontTools/ttLib/tables/__init__.py
index 79a50fdc..bbfb8b70 100644
--- a/Lib/fontTools/ttLib/tables/__init__.py
+++ b/Lib/fontTools/ttLib/tables/__init__.py
@@ -1,7 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
# DON'T EDIT! This file is generated by MetaTools/buildTableList.py.
def _moduleFinderHint():
"""Dummy function to let modulefinder know what tables may be
@@ -17,6 +14,7 @@ def _moduleFinderHint():
from . import C_O_L_R_
from . import C_P_A_L_
from . import D_S_I_G_
+ from . import D__e_b_g
from . import E_B_D_T_
from . import E_B_L_C_
from . import F_F_T_M_
@@ -41,6 +39,7 @@ def _moduleFinderHint():
from . import S__i_l_f
from . import S__i_l_l
from . import T_S_I_B_
+ from . import T_S_I_C_
from . import T_S_I_D_
from . import T_S_I_J_
from . import T_S_I_P_
diff --git a/Lib/fontTools/ttLib/tables/_a_n_k_r.py b/Lib/fontTools/ttLib/tables/_a_n_k_r.py
index 3a1aa08d..1f2946c2 100644
--- a/Lib/fontTools/ttLib/tables/_a_n_k_r.py
+++ b/Lib/fontTools/ttLib/tables/_a_n_k_r.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py
index 57601c5d..2b6a40ed 100644
--- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py
+++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py
@@ -1,12 +1,13 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools import ttLib
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
-from fontTools.misc.fixedTools import fixedToFloat, floatToFixed
-from fontTools.misc.textTools import safeEval
+from fontTools.misc.fixedTools import (
+ fixedToFloat as fi2fl,
+ floatToFixed as fl2fi,
+ floatToFixedToStr as fl2str,
+ strToFixedToFloat as str2fl,
+)
from fontTools.ttLib import TTLibError
from . import DefaultTable
-import array
import struct
import logging
@@ -47,8 +48,8 @@ class table__a_v_a_r(DefaultTable.DefaultTable):
mappings = sorted(self.segments[axis].items())
result.append(struct.pack(">H", len(mappings)))
for key, value in mappings:
- fixedKey = floatToFixed(key, 14)
- fixedValue = floatToFixed(value, 14)
+ fixedKey = fl2fi(key, 14)
+ fixedValue = fl2fi(value, 14)
result.append(struct.pack(">hh", fixedKey, fixedValue))
return bytesjoin(result)
@@ -67,7 +68,7 @@ class table__a_v_a_r(DefaultTable.DefaultTable):
pos = pos + 2
for _ in range(numPairs):
fromValue, toValue = struct.unpack(">hh", data[pos:pos+4])
- segments[fixedToFloat(fromValue, 14)] = fixedToFloat(toValue, 14)
+ segments[fi2fl(fromValue, 14)] = fi2fl(toValue, 14)
pos = pos + 4
def toXML(self, writer, ttFont):
@@ -76,10 +77,8 @@ class table__a_v_a_r(DefaultTable.DefaultTable):
writer.begintag("segment", axis=axis)
writer.newline()
for key, value in sorted(self.segments[axis].items()):
- # roundtrip float -> fixed -> float to normalize TTX output
- # as dumped after decompiling or straight from varLib
- key = fixedToFloat(floatToFixed(key, 14), 14)
- value = fixedToFloat(floatToFixed(value, 14), 14)
+ key = fl2str(key, 14)
+ value = fl2str(value, 14)
writer.simpletag("mapping", **{"from": key, "to": value})
writer.newline()
writer.endtag("segment")
@@ -93,8 +92,8 @@ class table__a_v_a_r(DefaultTable.DefaultTable):
if isinstance(element, tuple):
elementName, elementAttrs, _ = element
if elementName == "mapping":
- fromValue = safeEval(elementAttrs["from"])
- toValue = safeEval(elementAttrs["to"])
+ fromValue = str2fl(elementAttrs["from"], 14)
+ toValue = str2fl(elementAttrs["to"], 14)
if fromValue in segment:
log.warning("duplicate entry for %s in axis '%s'",
fromValue, axis)
diff --git a/Lib/fontTools/ttLib/tables/_b_s_l_n.py b/Lib/fontTools/ttLib/tables/_b_s_l_n.py
index 31d83995..8e266fa5 100644
--- a/Lib/fontTools/ttLib/tables/_b_s_l_n.py
+++ b/Lib/fontTools/ttLib/tables/_b_s_l_n.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_c_i_d_g.py b/Lib/fontTools/ttLib/tables/_c_i_d_g.py
index 1d6c5028..de83d4d6 100644
--- a/Lib/fontTools/ttLib/tables/_c_i_d_g.py
+++ b/Lib/fontTools/ttLib/tables/_c_i_d_g.py
@@ -1,6 +1,4 @@
# coding: utf-8
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
index 971a3163..a65a0c25 100644
--- a/Lib/fontTools/ttLib/tables/_c_m_a_p.py
+++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc.textTools import safeEval, readHex
from fontTools.misc.encodingTools import getEncoding
from fontTools.ttLib import getSearchRange
@@ -19,7 +18,7 @@ def _make_map(font, chars, gids):
cmap = {}
glyphOrder = font.getGlyphOrder()
for char,gid in zip(chars,gids):
- if gid is 0:
+ if gid == 0:
continue
try:
name = glyphOrder[gid]
@@ -253,7 +252,7 @@ class cmap_format_0(CmapSubtable):
data = self.data # decompileHeader assigns the data after the header to self.data
assert 262 == self.length, "Format 0 cmap subtable not 262 bytes"
gids = array.array("B")
- gids.fromstring(self.data)
+ gids.frombytes(self.data)
charCodes = list(range(len(gids)))
self.cmap = _make_map(self.ttFont, charCodes, gids)
@@ -267,7 +266,7 @@ class cmap_format_0(CmapSubtable):
valueList = [getGlyphID(cmap[i]) if i in cmap else 0 for i in range(256)]
gids = array.array("B", valueList)
- data = struct.pack(">HHH", 0, 262, self.language) + gids.tostring()
+ data = struct.pack(">HHH", 0, 262, self.language) + gids.tobytes()
assert len(data) == 262
return data
@@ -337,7 +336,7 @@ class cmap_format_2(CmapSubtable):
maxSubHeaderindex = 0
# get the key array, and determine the number of subHeaders.
allKeys = array.array("H")
- allKeys.fromstring(data[:512])
+ allKeys.frombytes(data[:512])
data = data[512:]
if sys.byteorder != "big": allKeys.byteswap()
subHeaderKeys = [ key//8 for key in allKeys]
@@ -353,7 +352,7 @@ class cmap_format_2(CmapSubtable):
pos += 8
giDataPos = pos + subHeader.idRangeOffset-2
giList = array.array("H")
- giList.fromstring(data[giDataPos:giDataPos + subHeader.entryCount*2])
+ giList.frombytes(data[giDataPos:giDataPos + subHeader.entryCount*2])
if sys.byteorder != "big": giList.byteswap()
subHeader.glyphIndexArray = giList
subHeaderList.append(subHeader)
@@ -695,7 +694,7 @@ class cmap_format_4(CmapSubtable):
segCount = segCountX2 // 2
allCodes = array.array("H")
- allCodes.fromstring(data)
+ allCodes.frombytes(data)
self.data = data = None
if sys.byteorder != "big": allCodes.byteswap()
@@ -827,7 +826,7 @@ class cmap_format_4(CmapSubtable):
if sys.byteorder != "big": charCodeArray.byteswap()
if sys.byteorder != "big": idDeltaArray.byteswap()
if sys.byteorder != "big": restArray.byteswap()
- data = charCodeArray.tostring() + idDeltaArray.tostring() + restArray.tostring()
+ data = charCodeArray.tobytes() + idDeltaArray.tobytes() + restArray.tobytes()
length = struct.calcsize(cmap_format_4_format) + len(data)
header = struct.pack(cmap_format_4_format, self.format, length, self.language,
@@ -865,7 +864,7 @@ class cmap_format_6(CmapSubtable):
data = data[4:]
#assert len(data) == 2 * entryCount # XXX not true in Apple's Helvetica!!!
gids = array.array("H")
- gids.fromstring(data[:2 * int(entryCount)])
+ gids.frombytes(data[:2 * int(entryCount)])
if sys.byteorder != "big": gids.byteswap()
self.data = data = None
@@ -886,7 +885,7 @@ class cmap_format_6(CmapSubtable):
]
gids = array.array("H", valueList)
if sys.byteorder != "big": gids.byteswap()
- data = gids.tostring()
+ data = gids.tobytes()
else:
data = b""
firstCode = 0
diff --git a/Lib/fontTools/ttLib/tables/_c_v_a_r.py b/Lib/fontTools/ttLib/tables/_c_v_a_r.py
index c4ebbc92..09b2c16c 100644
--- a/Lib/fontTools/ttLib/tables/_c_v_a_r.py
+++ b/Lib/fontTools/ttLib/tables/_c_v_a_r.py
@@ -1,6 +1,4 @@
-from __future__ import \
- print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from . import DefaultTable
from fontTools.misc import sstruct
from fontTools.ttLib.tables.TupleVariation import \
diff --git a/Lib/fontTools/ttLib/tables/_c_v_t.py b/Lib/fontTools/ttLib/tables/_c_v_t.py
index 21df0bad..26395c93 100644
--- a/Lib/fontTools/ttLib/tables/_c_v_t.py
+++ b/Lib/fontTools/ttLib/tables/_c_v_t.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import sys
@@ -9,14 +7,14 @@ class table__c_v_t(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
values = array.array("h")
- values.fromstring(data)
+ values.frombytes(data)
if sys.byteorder != "big": values.byteswap()
self.values = values
def compile(self, ttFont):
values = self.values[:]
if sys.byteorder != "big": values.byteswap()
- return values.tostring()
+ return values.tobytes()
def toXML(self, writer, ttFont):
for i in range(len(self.values)):
diff --git a/Lib/fontTools/ttLib/tables/_f_e_a_t.py b/Lib/fontTools/ttLib/tables/_f_e_a_t.py
index 37152714..eb03f8ba 100644
--- a/Lib/fontTools/ttLib/tables/_f_e_a_t.py
+++ b/Lib/fontTools/ttLib/tables/_f_e_a_t.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_f_p_g_m.py b/Lib/fontTools/ttLib/tables/_f_p_g_m.py
index 6536dba9..ec3576ce 100644
--- a/Lib/fontTools/ttLib/tables/_f_p_g_m.py
+++ b/Lib/fontTools/ttLib/tables/_f_p_g_m.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from . import DefaultTable
from . import ttProgram
diff --git a/Lib/fontTools/ttLib/tables/_f_v_a_r.py b/Lib/fontTools/ttLib/tables/_f_v_a_r.py
index 3bc46e24..7487da62 100644
--- a/Lib/fontTools/ttLib/tables/_f_v_a_r.py
+++ b/Lib/fontTools/ttLib/tables/_f_v_a_r.py
@@ -1,8 +1,12 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, bytesjoin
from fontTools.misc import sstruct
-from fontTools.misc.fixedTools import fixedToFloat, floatToFixed
-from fontTools.misc.textTools import safeEval, num2binary, binary2num
+from fontTools.misc.fixedTools import (
+ fixedToFloat as fi2fl,
+ floatToFixed as fl2fi,
+ floatToFixedToStr as fl2str,
+ strToFixedToFloat as str2fl,
+)
+from fontTools.misc.textTools import safeEval
from fontTools.ttLib import TTLibError
from . import DefaultTable
import struct
@@ -130,9 +134,9 @@ class Axis(object):
writer.newline()
for tag, value in [("AxisTag", self.axisTag),
("Flags", "0x%X" % self.flags),
- ("MinValue", str(self.minValue)),
- ("DefaultValue", str(self.defaultValue)),
- ("MaxValue", str(self.maxValue)),
+ ("MinValue", fl2str(self.minValue, 16)),
+ ("DefaultValue", fl2str(self.defaultValue, 16)),
+ ("MaxValue", fl2str(self.maxValue, 16)),
("AxisNameID", str(self.axisNameID))]:
writer.begintag(tag)
writer.write(value)
@@ -149,7 +153,11 @@ class Axis(object):
self.axisTag = Tag(value)
elif tag in {"Flags", "MinValue", "DefaultValue", "MaxValue",
"AxisNameID"}:
- setattr(self, tag[0].lower() + tag[1:], safeEval(value))
+ setattr(
+ self,
+ tag[0].lower() + tag[1:],
+ str2fl(value, 16) if tag.endswith("Value") else safeEval(value)
+ )
class NamedInstance(object):
@@ -162,7 +170,7 @@ class NamedInstance(object):
def compile(self, axisTags, includePostScriptName):
result = [sstruct.pack(FVAR_INSTANCE_FORMAT, self)]
for axis in axisTags:
- fixedCoord = floatToFixed(self.coordinates[axis], 16)
+ fixedCoord = fl2fi(self.coordinates[axis], 16)
result.append(struct.pack(">l", fixedCoord))
if includePostScriptName:
result.append(struct.pack(">H", self.postscriptNameID))
@@ -173,7 +181,7 @@ class NamedInstance(object):
pos = sstruct.calcsize(FVAR_INSTANCE_FORMAT)
for axis in axisTags:
value = struct.unpack(">l", data[pos : pos + 4])[0]
- self.coordinates[axis] = fixedToFloat(value, 16)
+ self.coordinates[axis] = fi2fl(value, 16)
pos += 4
if pos + 2 <= len(data):
self.postscriptNameID = struct.unpack(">H", data[pos : pos + 2])[0]
@@ -200,7 +208,7 @@ class NamedInstance(object):
writer.newline()
for axis in ttFont["fvar"].axes:
writer.simpletag("coord", axis=axis.axisTag,
- value=self.coordinates[axis.axisTag])
+ value=fl2str(self.coordinates[axis.axisTag], 16))
writer.newline()
writer.endtag("NamedInstance")
writer.newline()
@@ -216,4 +224,5 @@ class NamedInstance(object):
for tag, elementAttrs, _ in filter(lambda t: type(t) is tuple, content):
if tag == "coord":
- self.coordinates[elementAttrs["axis"]] = safeEval(elementAttrs["value"])
+ value = str2fl(elementAttrs["value"], 16)
+ self.coordinates[elementAttrs["axis"]] = value
diff --git a/Lib/fontTools/ttLib/tables/_g_a_s_p.py b/Lib/fontTools/ttLib/tables/_g_a_s_p.py
index dce35691..2c80913c 100644
--- a/Lib/fontTools/ttLib/tables/_g_a_s_p.py
+++ b/Lib/fontTools/ttLib/tables/_g_a_s_p.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import struct
diff --git a/Lib/fontTools/ttLib/tables/_g_c_i_d.py b/Lib/fontTools/ttLib/tables/_g_c_i_d.py
index f8b57e2a..2e746c84 100644
--- a/Lib/fontTools/ttLib/tables/_g_c_i_d.py
+++ b/Lib/fontTools/ttLib/tables/_g_c_i_d.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
index 7d2c16e6..4680ddbf 100644
--- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py
+++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
@@ -1,8 +1,7 @@
"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
-from __future__ import print_function, division, absolute_import
from collections import namedtuple
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, tostr
from fontTools.misc import sstruct
from fontTools import ttLib
from fontTools import version
@@ -12,6 +11,8 @@ from fontTools.misc.bezierTools import calcQuadraticBounds
from fontTools.misc.fixedTools import (
fixedToFloat as fi2fl,
floatToFixed as fl2fi,
+ floatToFixedToStr as fl2str,
+ strToFixedToFloat as str2fl,
otRound,
)
from numbers import Number
@@ -55,7 +56,8 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
loca = ttFont['loca']
- last = int(loca[0])
+ pos = int(loca[0])
+ nextPos = 0
noname = 0
self.glyphs = {}
self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
@@ -65,17 +67,17 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
except IndexError:
noname = noname + 1
glyphName = 'ttxautoglyph%s' % i
- next = int(loca[i+1])
- glyphdata = data[last:next]
- if len(glyphdata) != (next - last):
+ nextPos = int(loca[i+1])
+ glyphdata = data[pos:nextPos]
+ if len(glyphdata) != (nextPos - pos):
raise ttLib.TTLibError("not enough 'glyf' table data")
glyph = Glyph(glyphdata)
self.glyphs[glyphName] = glyph
- last = next
- if len(data) - next >= 4:
+ pos = nextPos
+ if len(data) - nextPos >= 4:
log.warning(
"too much 'glyf' table data: expected %d, received %d bytes",
- next, len(data))
+ nextPos, len(data))
if noname:
log.warning('%s glyphs have no name', noname)
if ttFont.lazy is False: # Be lazy for None and True
@@ -120,6 +122,12 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
ttFont['loca'].set(locations)
if 'maxp' in ttFont:
ttFont['maxp'].numGlyphs = len(self.glyphs)
+ if not data:
+ # As a special case when all glyph in the font are empty, add a zero byte
+ # to the table, so that OTS doesn’t reject it, and to make the table work
+ # on Windows as well.
+ # See https://github.com/khaledhosny/ots/issues/52
+ data = b"\0"
return data
def toXML(self, writer, ttFont, splitGlyphs=False):
@@ -137,11 +145,14 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
path, ext = os.path.splitext(writer.file.name)
existingGlyphFiles = set()
for glyphName in glyphNames:
+ if glyphName not in self:
+ log.warning("glyph '%s' does not exist in glyf table", glyphName)
+ continue
glyph = self[glyphName]
if glyph.numberOfContours:
if splitGlyphs:
glyphPath = userNameToFileName(
- tounicode(glyphName, 'utf-8'),
+ tostr(glyphName, 'utf-8'),
existingGlyphFiles,
prefix=path + ".",
suffix=ext)
@@ -639,6 +650,7 @@ class Glyph(object):
assert self.isComposite()
nContours = 0
nPoints = 0
+ initialMaxComponentDepth = maxComponentDepth
for compo in self.components:
baseGlyph = glyfTable[compo.glyphName]
if baseGlyph.numberOfContours == 0:
@@ -646,8 +658,9 @@ class Glyph(object):
elif baseGlyph.numberOfContours > 0:
nP, nC = baseGlyph.getMaxpValues()
else:
- nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues(
- glyfTable, maxComponentDepth + 1)
+ nP, nC, componentDepth = baseGlyph.getCompositeMaxpValues(
+ glyfTable, initialMaxComponentDepth + 1)
+ maxComponentDepth = max(maxComponentDepth, componentDepth)
nPoints = nPoints + nP
nContours = nContours + nC
return CompositeMaxpValues(nPoints, nContours, maxComponentDepth)
@@ -678,7 +691,7 @@ class Glyph(object):
def decompileCoordinates(self, data):
endPtsOfContours = array.array("h")
- endPtsOfContours.fromstring(data[:2*self.numberOfContours])
+ endPtsOfContours.frombytes(data[:2*self.numberOfContours])
if sys.byteorder != "big": endPtsOfContours.byteswap()
self.endPtsOfContours = endPtsOfContours.tolist()
@@ -792,7 +805,7 @@ class Glyph(object):
data = []
endPtsOfContours = array.array("h", self.endPtsOfContours)
if sys.byteorder != "big": endPtsOfContours.byteswap()
- data.append(endPtsOfContours.tostring())
+ data.append(endPtsOfContours.tobytes())
instructions = self.program.getBytecode()
data.append(struct.pack(">h", len(instructions)))
data.append(instructions)
@@ -856,7 +869,7 @@ class Glyph(object):
repeat = 0
compressedflags.append(flag)
lastflag = flag
- compressedFlags = array.array("B", compressedflags).tostring()
+ compressedFlags = array.array("B", compressedflags).tobytes()
compressedXs = bytesjoin(xPoints)
compressedYs = bytesjoin(yPoints)
return (compressedFlags, compressedXs, compressedYs)
@@ -911,9 +924,9 @@ class Glyph(object):
raise Exception("internal error")
except StopIteration:
pass
- compressedFlags = compressedFlags.tostring()
- compressedXs = compressedXs.tostring()
- compressedYs = compressedYs.tostring()
+ compressedFlags = compressedFlags.tobytes()
+ compressedXs = compressedXs.tobytes()
+ compressedYs = compressedYs.tobytes()
return (compressedFlags, compressedXs, compressedYs)
@@ -1001,33 +1014,37 @@ class Glyph(object):
coordinates, endPts, flags = g.getCoordinates(glyfTable)
except RecursionError:
raise ttLib.TTLibError("glyph '%s' contains a recursive component reference" % compo.glyphName)
+ coordinates = GlyphCoordinates(coordinates)
if hasattr(compo, "firstPt"):
- # move according to two reference points
+ # component uses two reference points: we apply the transform _before_
+ # computing the offset between the points
+ if hasattr(compo, "transform"):
+ coordinates.transform(compo.transform)
x1,y1 = allCoords[compo.firstPt]
x2,y2 = coordinates[compo.secondPt]
move = x1-x2, y1-y2
- else:
- move = compo.x, compo.y
-
- coordinates = GlyphCoordinates(coordinates)
- if not hasattr(compo, "transform"):
coordinates.translate(move)
else:
- apple_way = compo.flags & SCALED_COMPONENT_OFFSET
- ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
- assert not (apple_way and ms_way)
- if not (apple_way or ms_way):
- scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT # see top of this file
- else:
- scale_component_offset = apple_way
- if scale_component_offset:
- # the Apple way: first move, then scale (ie. scale the component offset)
+ # component uses XY offsets
+ move = compo.x, compo.y
+ if not hasattr(compo, "transform"):
coordinates.translate(move)
- coordinates.transform(compo.transform)
else:
- # the MS way: first scale, then move
- coordinates.transform(compo.transform)
- coordinates.translate(move)
+ apple_way = compo.flags & SCALED_COMPONENT_OFFSET
+ ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
+ assert not (apple_way and ms_way)
+ if not (apple_way or ms_way):
+ scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT # see top of this file
+ else:
+ scale_component_offset = apple_way
+ if scale_component_offset:
+ # the Apple way: first move, then scale (ie. scale the component offset)
+ coordinates.translate(move)
+ coordinates.transform(compo.transform)
+ else:
+ # the MS way: first scale, then move
+ coordinates.transform(compo.transform)
+ coordinates.translate(move)
offset = len(allCoords)
allEndPts.extend(e + offset for e in endPts)
allCoords.extend(coordinates)
@@ -1153,7 +1170,7 @@ class Glyph(object):
# Remove padding
data = data[:i]
- self.data = data.tostring()
+ self.data = data.tobytes()
def removeHinting(self):
self.trim (remove_hinting=True)
@@ -1174,7 +1191,7 @@ class Glyph(object):
for end in endPts:
end = end + 1
contour = coordinates[start:end]
- cFlags = flags[start:end]
+ cFlags = [flagOnCurve & f for f in flags[start:end]]
start = end
if 1 not in cFlags:
# There is not a single on-curve point on the curve,
@@ -1193,7 +1210,10 @@ class Glyph(object):
while contour:
nextOnCurve = cFlags.index(1) + 1
if nextOnCurve == 1:
- pen.lineTo(contour[0])
+ # Skip a final lineTo(), as it is implied by
+ # pen.closePath()
+ if len(contour) > 1:
+ pen.lineTo(contour[0])
else:
pen.qCurveTo(*contour[:nextOnCurve])
contour = contour[nextOnCurve:]
@@ -1225,7 +1245,7 @@ class Glyph(object):
# Start with the appropriate segment type based on the final segment
segmentType = "line" if cFlags[-1] == 1 else "qcurve"
for i, pt in enumerate(contour):
- if cFlags[i] == 1:
+ if cFlags[i] & flagOnCurve == 1:
pen.addPoint(pt, segmentType=segmentType)
segmentType = "line"
else:
@@ -1362,15 +1382,18 @@ class GlyphComponent(object):
transform = self.transform
if transform[0][1] or transform[1][0]:
attrs = attrs + [
- ("scalex", transform[0][0]), ("scale01", transform[0][1]),
- ("scale10", transform[1][0]), ("scaley", transform[1][1]),
- ]
+ ("scalex", fl2str(transform[0][0], 14)),
+ ("scale01", fl2str(transform[0][1], 14)),
+ ("scale10", fl2str(transform[1][0], 14)),
+ ("scaley", fl2str(transform[1][1], 14)),
+ ]
elif transform[0][0] != transform[1][1]:
attrs = attrs + [
- ("scalex", transform[0][0]), ("scaley", transform[1][1]),
- ]
+ ("scalex", fl2str(transform[0][0], 14)),
+ ("scaley", fl2str(transform[1][1], 14)),
+ ]
else:
- attrs = attrs + [("scale", transform[0][0])]
+ attrs = attrs + [("scale", fl2str(transform[0][0], 14))]
attrs = attrs + [("flags", hex(self.flags))]
writer.simpletag("component", attrs)
writer.newline()
@@ -1384,17 +1407,17 @@ class GlyphComponent(object):
self.x = safeEval(attrs["x"])
self.y = safeEval(attrs["y"])
if "scale01" in attrs:
- scalex = safeEval(attrs["scalex"])
- scale01 = safeEval(attrs["scale01"])
- scale10 = safeEval(attrs["scale10"])
- scaley = safeEval(attrs["scaley"])
+ scalex = str2fl(attrs["scalex"], 14)
+ scale01 = str2fl(attrs["scale01"], 14)
+ scale10 = str2fl(attrs["scale10"], 14)
+ scaley = str2fl(attrs["scaley"], 14)
self.transform = [[scalex, scale01], [scale10, scaley]]
elif "scalex" in attrs:
- scalex = safeEval(attrs["scalex"])
- scaley = safeEval(attrs["scaley"])
+ scalex = str2fl(attrs["scalex"], 14)
+ scaley = str2fl(attrs["scaley"], 14)
self.transform = [[scalex, 0], [0, scaley]]
elif "scale" in attrs:
- scale = safeEval(attrs["scale"])
+ scale = str2fl(attrs["scale"], 14)
self.transform = [[scale, 0], [0, scale]]
self.flags = safeEval(attrs["flags"])
@@ -1483,12 +1506,12 @@ class GlyphCoordinates(object):
p = self._checkFloat(p)
self._a.extend(p)
- def toInt(self):
+ def toInt(self, *, round=otRound):
if not self.isFloat():
return
a = array.array("h")
for n in self._a:
- a.append(otRound(n))
+ a.append(round(n))
self._a = a
def relativeToAbsolute(self):
@@ -1603,13 +1626,9 @@ class GlyphCoordinates(object):
for i in range(len(a)):
a[i] = -a[i]
return r
- def __round__(self):
- """
- Note: This is Python 3 only. Python 2 does not call __round__.
- As such, we cannot test this method either. :(
- """
+ def __round__(self, *, round=otRound):
r = self.copy()
- r.toInt()
+ r.toInt(round=round)
return r
def __add__(self, other): return self.copy().__iadd__(other)
diff --git a/Lib/fontTools/ttLib/tables/_g_v_a_r.py b/Lib/fontTools/ttLib/tables/_g_v_a_r.py
index 7a02f8f5..8c9b530e 100644
--- a/Lib/fontTools/ttLib/tables/_g_v_a_r.py
+++ b/Lib/fontTools/ttLib/tables/_g_v_a_r.py
@@ -1,9 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools import ttLib
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
-from fontTools.ttLib import TTLibError
from . import DefaultTable
import array
import itertools
@@ -127,7 +124,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
# Long format: array of UInt32
offsets = array.array("I")
offsetsSize = (glyphCount + 1) * 4
- offsets.fromstring(data[0 : offsetsSize])
+ offsets.frombytes(data[0 : offsetsSize])
if sys.byteorder != "big": offsets.byteswap()
# In the short format, offsets need to be multiplied by 2.
@@ -159,7 +156,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
packed = array.array("I", offsets)
tableFormat = 1
if sys.byteorder != "big": packed.byteswap()
- return (packed.tostring(), tableFormat)
+ return (packed.tobytes(), tableFormat)
def toXML(self, writer, ttFont):
writer.simpletag("version", value=self.version)
@@ -167,7 +164,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
writer.simpletag("reserved", value=self.reserved)
writer.newline()
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
- for glyphName in ttFont.getGlyphOrder():
+ for glyphName in ttFont.getGlyphNames():
variations = self.variations.get(glyphName)
if not variations:
continue
diff --git a/Lib/fontTools/ttLib/tables/_h_d_m_x.py b/Lib/fontTools/ttLib/tables/_h_d_m_x.py
index e073325a..954d1bc1 100644
--- a/Lib/fontTools/ttLib/tables/_h_d_m_x.py
+++ b/Lib/fontTools/ttLib/tables/_h_d_m_x.py
@@ -1,8 +1,8 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, strjoin
from fontTools.misc import sstruct
from . import DefaultTable
import array
+from collections.abc import Mapping
hdmxHeaderFormat = """
> # big endian!
@@ -11,11 +11,6 @@ hdmxHeaderFormat = """
recordSize: l
"""
-try:
- from collections.abc import Mapping
-except:
- from UserDict import DictMixin as Mapping
-
class _GlyphnamedList(Mapping):
def __init__(self, reverseGlyphOrder, data):
diff --git a/Lib/fontTools/ttLib/tables/_h_e_a_d.py b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
index 42fbb1d4..4d19da03 100644
--- a/Lib/fontTools/ttLib/tables/_h_e_a_d.py
+++ b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
@@ -1,10 +1,9 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
+from fontTools.misc.fixedTools import floatToFixedToStr, strToFixedToFloat
from fontTools.misc.textTools import safeEval, num2binary, binary2num
from fontTools.misc.timeTools import timestampFromString, timestampToString, timestampNow
from fontTools.misc.timeTools import epoch_diff as mac_epoch_diff # For backward compat
-from fontTools.misc.arrayTools import intRect
+from fontTools.misc.arrayTools import intRect, unionRect
from . import DefaultTable
import logging
@@ -34,14 +33,14 @@ headFormat = """
class table__h_e_a_d(DefaultTable.DefaultTable):
- dependencies = ['maxp', 'loca', 'CFF ']
+ dependencies = ['maxp', 'loca', 'CFF ', 'CFF2']
def decompile(self, data, ttFont):
dummy, rest = sstruct.unpack2(headFormat, data, self)
if rest:
# this is quite illegal, but there seem to be fonts out there that do this
log.warning("extra bytes at the end of 'head' table")
- assert rest == "\0\0"
+ assert rest == b"\0\0"
# For timestamp fields, ignore the top four bytes. Some fonts have
# bogus values there. Since till 2038 those bytes only can be zero,
@@ -65,6 +64,19 @@ class table__h_e_a_d(DefaultTable.DefaultTable):
if 'CFF ' in ttFont:
topDict = ttFont['CFF '].cff.topDictIndex[0]
self.xMin, self.yMin, self.xMax, self.yMax = intRect(topDict.FontBBox)
+ elif 'CFF2' in ttFont:
+ topDict = ttFont['CFF2'].cff.topDictIndex[0]
+ charStrings = topDict.CharStrings
+ fontBBox = None
+ for charString in charStrings.values():
+ bounds = charString.calcBounds(charStrings)
+ if bounds is not None:
+ if fontBBox is not None:
+ fontBBox = unionRect(fontBBox, bounds)
+ else:
+ fontBBox = bounds
+ if fontBBox is not None:
+ self.xMin, self.yMin, self.xMax, self.yMax = intRect(fontBBox)
if ttFont.recalcTimestamp:
self.modified = timestampNow()
data = sstruct.pack(headFormat, self)
@@ -73,12 +85,14 @@ class table__h_e_a_d(DefaultTable.DefaultTable):
def toXML(self, writer, ttFont):
writer.comment("Most of this table will be recalculated by the compiler")
writer.newline()
- formatstring, names, fixes = sstruct.getformat(headFormat)
+ _, names, fixes = sstruct.getformat(headFormat)
for name in names:
value = getattr(self, name)
- if name in ("created", "modified"):
+ if name in fixes:
+ value = floatToFixedToStr(value, precisionBits=fixes[name])
+ elif name in ("created", "modified"):
value = timestampToString(value)
- if name in ("magicNumber", "checkSumAdjustment"):
+ elif name in ("magicNumber", "checkSumAdjustment"):
if value < 0:
value = value + 0x100000000
value = hex(value)
@@ -91,7 +105,10 @@ class table__h_e_a_d(DefaultTable.DefaultTable):
def fromXML(self, name, attrs, content, ttFont):
value = attrs["value"]
- if name in ("created", "modified"):
+ fixes = sstruct.getformat(headFormat)[2]
+ if name in fixes:
+ value = strToFixedToFloat(value, precisionBits=fixes[name])
+ elif name in ("created", "modified"):
value = timestampFromString(value)
elif name in ("macStyle", "flags"):
value = binary2num(value)
diff --git a/Lib/fontTools/ttLib/tables/_h_h_e_a.py b/Lib/fontTools/ttLib/tables/_h_h_e_a.py
index bde9073c..9b8baaad 100644
--- a/Lib/fontTools/ttLib/tables/_h_h_e_a.py
+++ b/Lib/fontTools/ttLib/tables/_h_h_e_a.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from fontTools.misc.fixedTools import (
@@ -34,13 +32,26 @@ class table__h_h_e_a(DefaultTable.DefaultTable):
# Note: Keep in sync with table__v_h_e_a
- dependencies = ['hmtx', 'glyf', 'CFF ']
+ dependencies = ['hmtx', 'glyf', 'CFF ', 'CFF2']
+
+ # OpenType spec renamed these, add aliases for compatibility
+ @property
+ def ascender(self): return self.ascent
+
+ @ascender.setter
+ def ascender(self,value): self.ascent = value
+
+ @property
+ def descender(self): return self.descent
+
+ @descender.setter
+ def descender(self,value): self.descent = value
def decompile(self, data, ttFont):
sstruct.unpack(hheaFormat, data, self)
def compile(self, ttFont):
- if ttFont.recalcBBoxes and (ttFont.isLoaded('glyf') or ttFont.isLoaded('CFF ')):
+ if ttFont.recalcBBoxes and (ttFont.isLoaded('glyf') or ttFont.isLoaded('CFF ') or ttFont.isLoaded('CFF2')):
self.recalc(ttFont)
self.tableVersion = fi2ve(self.tableVersion)
return sstruct.pack(hheaFormat, self)
@@ -62,8 +73,11 @@ class table__h_h_e_a(DefaultTable.DefaultTable):
# Calculate those.
g.recalcBounds(glyfTable)
boundsWidthDict[name] = g.xMax - g.xMin
- elif 'CFF ' in ttFont:
- topDict = ttFont['CFF '].cff.topDictIndex[0]
+ elif 'CFF ' in ttFont or 'CFF2' in ttFont:
+ if 'CFF ' in ttFont:
+ topDict = ttFont['CFF '].cff.topDictIndex[0]
+ else:
+ topDict = ttFont['CFF2'].cff.topDictIndex[0]
charStrings = topDict.CharStrings
for name in ttFont.getGlyphOrder():
cs = charStrings[name]
diff --git a/Lib/fontTools/ttLib/tables/_h_m_t_x.py b/Lib/fontTools/ttLib/tables/_h_m_t_x.py
index 24cad4f7..6980b8d8 100644
--- a/Lib/fontTools/ttLib/tables/_h_m_t_x.py
+++ b/Lib/fontTools/ttLib/tables/_h_m_t_x.py
@@ -1,6 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
from . import DefaultTable
@@ -109,7 +107,7 @@ class table__h_m_t_x(DefaultTable.DefaultTable):
raise
additionalMetrics = array.array("h", additionalMetrics)
if sys.byteorder != "big": additionalMetrics.byteswap()
- data = data + additionalMetrics.tostring()
+ data = data + additionalMetrics.tobytes()
return data
def toXML(self, writer, ttFont):
diff --git a/Lib/fontTools/ttLib/tables/_k_e_r_n.py b/Lib/fontTools/ttLib/tables/_k_e_r_n.py
index 1e5140bc..f3f714b2 100644
--- a/Lib/fontTools/ttLib/tables/_k_e_r_n.py
+++ b/Lib/fontTools/ttLib/tables/_k_e_r_n.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import getSearchRange
from fontTools.misc.textTools import safeEval, readHex
from fontTools.misc.fixedTools import (
diff --git a/Lib/fontTools/ttLib/tables/_l_c_a_r.py b/Lib/fontTools/ttLib/tables/_l_c_a_r.py
index 4b9c8544..e63310ef 100644
--- a/Lib/fontTools/ttLib/tables/_l_c_a_r.py
+++ b/Lib/fontTools/ttLib/tables/_l_c_a_r.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_l_o_c_a.py b/Lib/fontTools/ttLib/tables/_l_o_c_a.py
index 6aa53030..6a8693ed 100644
--- a/Lib/fontTools/ttLib/tables/_l_o_c_a.py
+++ b/Lib/fontTools/ttLib/tables/_l_o_c_a.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from . import DefaultTable
import sys
import array
@@ -20,7 +18,7 @@ class table__l_o_c_a(DefaultTable.DefaultTable):
else:
format = "H"
locations = array.array(format)
- locations.fromstring(data)
+ locations.frombytes(data)
if sys.byteorder != "big": locations.byteswap()
if not longFormat:
l = array.array("I")
@@ -47,7 +45,7 @@ class table__l_o_c_a(DefaultTable.DefaultTable):
locations = array.array("I", self.locations)
ttFont['head'].indexToLocFormat = 1
if sys.byteorder != "big": locations.byteswap()
- return locations.tostring()
+ return locations.tobytes()
def set(self, locations):
self.locations = array.array("I", locations)
diff --git a/Lib/fontTools/ttLib/tables/_l_t_a_g.py b/Lib/fontTools/ttLib/tables/_l_t_a_g.py
index 59f8e721..caec72a3 100644
--- a/Lib/fontTools/ttLib/tables/_l_t_a_g.py
+++ b/Lib/fontTools/ttLib/tables/_l_t_a_g.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, tobytes
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import struct
diff --git a/Lib/fontTools/ttLib/tables/_m_a_x_p.py b/Lib/fontTools/ttLib/tables/_m_a_x_p.py
index 7da30b43..e810806d 100644
--- a/Lib/fontTools/ttLib/tables/_m_a_x_p.py
+++ b/Lib/fontTools/ttLib/tables/_m_a_x_p.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/_m_e_t_a.py b/Lib/fontTools/ttLib/tables/_m_e_t_a.py
index cc19fe6e..1a125f82 100644
--- a/Lib/fontTools/ttLib/tables/_m_e_t_a.py
+++ b/Lib/fontTools/ttLib/tables/_m_e_t_a.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, strjoin
from fontTools.misc import sstruct
from fontTools.misc.textTools import readHex
from fontTools.ttLib import TTLibError
@@ -86,7 +85,11 @@ class table__m_e_t_a(DefaultTable.DefaultTable):
else:
writer.begintag("hexdata", tag=tag)
writer.newline()
- writer.dumphex(self.data[tag])
+ data = self.data[tag]
+ if min(data) >= 0x20 and max(data) <= 0x7E:
+ writer.comment("ascii: " + data.decode("ascii"))
+ writer.newline()
+ writer.dumphex(data)
writer.endtag("hexdata")
writer.newline()
diff --git a/Lib/fontTools/ttLib/tables/_m_o_r_t.py b/Lib/fontTools/ttLib/tables/_m_o_r_t.py
index b87b4254..261e593e 100644
--- a/Lib/fontTools/ttLib/tables/_m_o_r_t.py
+++ b/Lib/fontTools/ttLib/tables/_m_o_r_t.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_m_o_r_x.py b/Lib/fontTools/ttLib/tables/_m_o_r_x.py
index 1619d8d6..da299c6d 100644
--- a/Lib/fontTools/ttLib/tables/_m_o_r_x.py
+++ b/Lib/fontTools/ttLib/tables/_m_o_r_x.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_n_a_m_e.py b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
index 488c4ea5..206469de 100644
--- a/Lib/fontTools/ttLib/tables/_n_a_m_e.py
+++ b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, strjoin, tobytes, tostr
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from fontTools.misc.encodingTools import getEncoding
@@ -135,7 +133,7 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
"""
if not hasattr(self, 'names'):
self.names = []
- if not isinstance(string, unicode):
+ if not isinstance(string, str):
if isinstance(string, bytes):
log.warning(
"name string is bytes, ensure it's correctly encoded: %r", string)
@@ -149,6 +147,31 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
else:
self.names.append(makeName(string, nameID, platformID, platEncID, langID))
+ def removeNames(self, nameID=None, platformID=None, platEncID=None, langID=None):
+ """Remove any name records identified by the given combination of 'nameID',
+ 'platformID', 'platEncID' and 'langID'.
+ """
+ args = {
+ argName: argValue
+ for argName, argValue in (
+ ("nameID", nameID),
+ ("platformID", platformID),
+ ("platEncID", platEncID),
+ ("langID", langID),
+ )
+ if argValue is not None
+ }
+ if not args:
+ # no arguments, nothing to do
+ return
+ self.names = [
+ rec for rec in self.names
+ if any(
+ argValue != getattr(rec, argName)
+ for argName, argValue in args.items()
+ )
+ ]
+
def _findUnusedNameID(self, minNameID=256):
"""Finds an unused name id.
@@ -161,8 +184,65 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
raise ValueError("nameID must be less than 32768")
return nameID
+ def findMultilingualName(self, names, windows=True, mac=True, minNameID=0):
+ """Return the name ID of an existing multilingual name that
+ matches the 'names' dictionary, or None if not found.
+
+ 'names' is a dictionary with the name in multiple languages,
+ such as {'en': 'Pale', 'de': 'Blaß', 'de-CH': 'Blass'}.
+ The keys can be arbitrary IETF BCP 47 language codes;
+ the values are Unicode strings.
+
+ If 'windows' is True, the returned name ID is guaranteed
+ exist for all requested languages for platformID=3 and
+ platEncID=1.
+ If 'mac' is True, the returned name ID is guaranteed to exist
+ for all requested languages for platformID=1 and platEncID=0.
+
+ The returned name ID will not be less than the 'minNameID'
+ argument.
+ """
+ # Gather the set of requested
+ # (string, platformID, platEncID, langID)
+ # tuples
+ reqNameSet = set()
+ for lang, name in sorted(names.items()):
+ if windows:
+ windowsName = _makeWindowsName(name, None, lang)
+ if windowsName is not None:
+ reqNameSet.add((windowsName.string,
+ windowsName.platformID,
+ windowsName.platEncID,
+ windowsName.langID))
+ if mac:
+ macName = _makeMacName(name, None, lang)
+ if macName is not None:
+ reqNameSet.add((macName.string,
+ macName.platformID,
+ macName.platEncID,
+ macName.langID))
+
+ # Collect matching name IDs
+ matchingNames = dict()
+ for name in self.names:
+ try:
+ key = (name.toUnicode(), name.platformID,
+ name.platEncID, name.langID)
+ except UnicodeDecodeError:
+ continue
+ if key in reqNameSet and name.nameID >= minNameID:
+ nameSet = matchingNames.setdefault(name.nameID, set())
+ nameSet.add(key)
+
+ # Return the first name ID that defines all requested strings
+ for nameID, nameSet in sorted(matchingNames.items()):
+ if nameSet == reqNameSet:
+ return nameID
+
+ return None # not found
+
def addMultilingualName(self, names, ttFont=None, nameID=None,
- windows=True, mac=True):
+ windows=True, mac=True, minNameID=0):
"""Add a multilingual name, returning its name ID
'names' is a dictionary with the name in multiple languages,
@@ -176,14 +256,23 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
names that otherwise cannot get encoded at all.
'nameID' is the name ID to be used, or None to let the library
- pick an unused name ID.
+ find an existing set of name records that match, or pick an
+ unused name ID.
If 'windows' is True, a platformID=3 name record will be added.
If 'mac' is True, a platformID=1 name record will be added.
+
+ If the 'nameID' argument is None, the created nameID will not
+ be less than the 'minNameID' argument.
"""
if not hasattr(self, 'names'):
self.names = []
if nameID is None:
+ # Reuse nameID if possible
+ nameID = self.findMultilingualName(
+ names, windows=windows, mac=mac, minNameID=minNameID)
+ if nameID is not None:
+ return nameID
nameID = self._findUnusedNameID()
# TODO: Should minimize BCP 47 language codes.
# https://github.com/fonttools/fonttools/issues/930
@@ -221,10 +310,9 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
"'platforms' must contain at least one (platformID, platEncID, langID) tuple"
if not hasattr(self, 'names'):
self.names = []
- if not isinstance(string, unicode):
+ if not isinstance(string, str):
raise TypeError(
- "expected %s, found %s: %r" % (
- unicode.__name__, type(string).__name__,string ))
+ "expected str, found %s: %r" % (type(string).__name__, string))
nameID = self._findUnusedNameID(minNameID + 1)
for platformID, platEncID, langID in platforms:
self.names.append(makeName(string, nameID, platformID, platEncID, langID))
@@ -352,7 +440,7 @@ class NameRecord(object):
encoding = self.getEncoding()
string = self.string
- if encoding == 'utf_16_be' and len(string) % 2 == 1:
+ if isinstance(string, bytes) and encoding == 'utf_16_be' and len(string) % 2 == 1:
# Recover badly encoded UTF-16 strings that have an odd number of bytes:
# - If the last byte is zero, drop it. Otherwise,
# - If all the odd bytes are zero and all the even bytes are ASCII,
@@ -368,7 +456,7 @@ class NameRecord(object):
elif byteord(string[0]) == 0 and all(isascii(byteord(b)) for b in string[1:]):
string = bytesjoin(b'\0'+bytechr(byteord(b)) for b in string[1:])
- string = tounicode(string, encoding=encoding, errors=errors)
+ string = tostr(string, encoding=encoding, errors=errors)
# If decoded strings still looks like UTF-16BE, it suggests a double-encoding.
# Fix it up.
@@ -392,13 +480,7 @@ class NameRecord(object):
"""
return tobytes(self.string, encoding=self.getEncoding(), errors=errors)
- def toStr(self, errors='strict'):
- if str == bytes:
- # python 2
- return self.toBytes(errors)
- else:
- # python 3
- return self.toUnicode(errors)
+ toStr = toUnicode
def toXML(self, writer, ttFont):
try:
@@ -442,22 +524,32 @@ class NameRecord(object):
if type(self) != type(other):
return NotImplemented
- # implemented so that list.sort() sorts according to the spec.
- selfTuple = (
- getattr(self, "platformID", None),
- getattr(self, "platEncID", None),
- getattr(self, "langID", None),
- getattr(self, "nameID", None),
- getattr(self, "string", None),
- )
- otherTuple = (
- getattr(other, "platformID", None),
- getattr(other, "platEncID", None),
- getattr(other, "langID", None),
- getattr(other, "nameID", None),
- getattr(other, "string", None),
- )
- return selfTuple < otherTuple
+ try:
+ # implemented so that list.sort() sorts according to the spec.
+ selfTuple = (
+ self.platformID,
+ self.platEncID,
+ self.langID,
+ self.nameID,
+ self.toBytes(),
+ )
+ otherTuple = (
+ other.platformID,
+ other.platEncID,
+ other.langID,
+ other.nameID,
+ other.toBytes(),
+ )
+ return selfTuple < otherTuple
+ except (UnicodeEncodeError, AttributeError):
+ # This can only happen for
+ # 1) an object that is not a NameRecord, or
+ # 2) an unlikely incomplete NameRecord object which has not been
+ # fully populated, or
+ # 3) when all IDs are identical but the strings can't be encoded
+ # for their platform encoding.
+ # In all cases it is best to return NotImplemented.
+ return NotImplemented
def __repr__(self):
return "<NameRecord NameID=%d; PlatformID=%d; LanguageID=%d>" % (
diff --git a/Lib/fontTools/ttLib/tables/_o_p_b_d.py b/Lib/fontTools/ttLib/tables/_o_p_b_d.py
index 60bb6c52..b22af216 100644
--- a/Lib/fontTools/ttLib/tables/_o_p_b_d.py
+++ b/Lib/fontTools/ttLib/tables/_o_p_b_d.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_p_o_s_t.py b/Lib/fontTools/ttLib/tables/_p_o_s_t.py
index 4874ecd9..e26e81f8 100644
--- a/Lib/fontTools/ttLib/tables/_p_o_s_t.py
+++ b/Lib/fontTools/ttLib/tables/_p_o_s_t.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, tobytes, tostr
from fontTools import ttLib
from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
from fontTools.misc import sstruct
@@ -83,7 +82,7 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
numGlyphs = ttFont['maxp'].numGlyphs
data = data[2:]
indices = array.array("H")
- indices.fromstring(data[:2*numGlyphs])
+ indices.frombytes(data[:2*numGlyphs])
if sys.byteorder != "big": indices.byteswap()
data = data[2*numGlyphs:]
self.extraNames = extraNames = unpackPStrings(data)
@@ -132,7 +131,7 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
from fontTools import agl
numGlyphs = ttFont['maxp'].numGlyphs
indices = array.array("H")
- indices.fromstring(data)
+ indices.frombytes(data)
if sys.byteorder != "big": indices.byteswap()
# In some older fonts, the size of the post table doesn't match
# the number of glyphs. Sometimes it's bigger, sometimes smaller.
@@ -172,7 +171,7 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
extraNames.append(psName)
indices.append(index)
if sys.byteorder != "big": indices.byteswap()
- return struct.pack(">H", numGlyphs) + indices.tostring() + packPStrings(extraNames)
+ return struct.pack(">H", numGlyphs) + indices.tobytes() + packPStrings(extraNames)
def encode_format_4_0(self, ttFont):
from fontTools import agl
@@ -189,7 +188,7 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
else:
indices.append(0xFFFF)
if sys.byteorder != "big": indices.byteswap()
- return indices.tostring()
+ return indices.tobytes()
def toXML(self, writer, ttFont):
formatstring, names, fixes = sstruct.getformat(postFormat)
diff --git a/Lib/fontTools/ttLib/tables/_p_r_e_p.py b/Lib/fontTools/ttLib/tables/_p_r_e_p.py
index f4e89254..7f517fb8 100644
--- a/Lib/fontTools/ttLib/tables/_p_r_e_p.py
+++ b/Lib/fontTools/ttLib/tables/_p_r_e_p.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools import ttLib
superclass = ttLib.getTableClass("fpgm")
diff --git a/Lib/fontTools/ttLib/tables/_p_r_o_p.py b/Lib/fontTools/ttLib/tables/_p_r_o_p.py
index 7da18eab..aead9d72 100644
--- a/Lib/fontTools/ttLib/tables/_p_r_o_p.py
+++ b/Lib/fontTools/ttLib/tables/_p_r_o_p.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_s_b_i_x.py b/Lib/fontTools/ttLib/tables/_s_b_i_x.py
index 6efd1f2f..c4b2ad38 100644
--- a/Lib/fontTools/ttLib/tables/_s_b_i_x.py
+++ b/Lib/fontTools/ttLib/tables/_s_b_i_x.py
@@ -1,10 +1,7 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval, num2binary, binary2num
from . import DefaultTable
-from .sbixGlyph import *
-from .sbixStrike import *
+from .sbixStrike import Strike
sbixHeaderFormat = """
diff --git a/Lib/fontTools/ttLib/tables/_t_r_a_k.py b/Lib/fontTools/ttLib/tables/_t_r_a_k.py
index b5820b27..7f3227dc 100644
--- a/Lib/fontTools/ttLib/tables/_t_r_a_k.py
+++ b/Lib/fontTools/ttLib/tables/_t_r_a_k.py
@@ -1,15 +1,16 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
-from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
+from fontTools.misc.fixedTools import (
+ fixedToFloat as fi2fl,
+ floatToFixed as fl2fi,
+ floatToFixedToStr as fl2str,
+ strToFixedToFloat as str2fl,
+)
from fontTools.misc.textTools import safeEval
from fontTools.ttLib import TTLibError
from . import DefaultTable
import struct
-try:
- from collections.abc import MutableMapping
-except ImportError:
- from UserDict import DictMixin as MutableMapping
+from collections.abc import MutableMapping
# Apple's documentation of 'trak':
@@ -258,19 +259,19 @@ class TrackTableEntry(MutableMapping):
name = ttFont["name"].getDebugName(self.nameIndex)
writer.begintag(
"trackEntry",
- (('value', self.track), ('nameIndex', self.nameIndex)))
+ (('value', fl2str(self.track, 16)), ('nameIndex', self.nameIndex)))
writer.newline()
if name:
writer.comment(name)
writer.newline()
for size, perSizeValue in sorted(self.items()):
- writer.simpletag("track", size=size, value=perSizeValue)
+ writer.simpletag("track", size=fl2str(size, 16), value=perSizeValue)
writer.newline()
writer.endtag("trackEntry")
writer.newline()
def fromXML(self, name, attrs, content, ttFont):
- self.track = safeEval(attrs['value'])
+ self.track = str2fl(attrs['value'], 16)
self.nameIndex = safeEval(attrs['nameIndex'])
for element in content:
if not isinstance(element, tuple):
@@ -278,7 +279,7 @@ class TrackTableEntry(MutableMapping):
name, attrs, _ = element
if name != 'track':
continue
- size = safeEval(attrs['size'])
+ size = str2fl(attrs['size'], 16)
self[size] = safeEval(attrs['value'])
def __getitem__(self, size):
diff --git a/Lib/fontTools/ttLib/tables/_v_h_e_a.py b/Lib/fontTools/ttLib/tables/_v_h_e_a.py
index 312c5eca..2bb24667 100644
--- a/Lib/fontTools/ttLib/tables/_v_h_e_a.py
+++ b/Lib/fontTools/ttLib/tables/_v_h_e_a.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from fontTools.misc.fixedTools import (
@@ -33,13 +31,13 @@ class table__v_h_e_a(DefaultTable.DefaultTable):
# Note: Keep in sync with table__h_h_e_a
- dependencies = ['vmtx', 'glyf', 'CFF ']
+ dependencies = ['vmtx', 'glyf', 'CFF ', 'CFF2']
def decompile(self, data, ttFont):
sstruct.unpack(vheaFormat, data, self)
def compile(self, ttFont):
- if ttFont.recalcBBoxes and (ttFont.isLoaded('glyf') or ttFont.isLoaded('CFF ')):
+ if ttFont.recalcBBoxes and (ttFont.isLoaded('glyf') or ttFont.isLoaded('CFF ') or ttFont.isLoaded('CFF2')):
self.recalc(ttFont)
self.tableVersion = fi2ve(self.tableVersion)
return sstruct.pack(vheaFormat, self)
@@ -61,8 +59,11 @@ class table__v_h_e_a(DefaultTable.DefaultTable):
# Calculate those.
g.recalcBounds(glyfTable)
boundsHeightDict[name] = g.yMax - g.yMin
- elif 'CFF ' in ttFont:
- topDict = ttFont['CFF '].cff.topDictIndex[0]
+ elif 'CFF ' in ttFont or 'CFF2' in ttFont:
+ if 'CFF ' in ttFont:
+ topDict = ttFont['CFF '].cff.topDictIndex[0]
+ else:
+ topDict = ttFont['CFF2'].cff.topDictIndex[0]
charStrings = topDict.CharStrings
for name in ttFont.getGlyphOrder():
cs = charStrings[name]
diff --git a/Lib/fontTools/ttLib/tables/_v_m_t_x.py b/Lib/fontTools/ttLib/tables/_v_m_t_x.py
index 5573225c..fc818d83 100644
--- a/Lib/fontTools/ttLib/tables/_v_m_t_x.py
+++ b/Lib/fontTools/ttLib/tables/_v_m_t_x.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools import ttLib
superclass = ttLib.getTableClass("hmtx")
diff --git a/Lib/fontTools/ttLib/tables/asciiTable.py b/Lib/fontTools/ttLib/tables/asciiTable.py
index 87266a08..7b036c8e 100644
--- a/Lib/fontTools/ttLib/tables/asciiTable.py
+++ b/Lib/fontTools/ttLib/tables/asciiTable.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import strjoin, tobytes, tostr
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py
index 816f1cd0..3c07f9e1 100644
--- a/Lib/fontTools/ttLib/tables/otBase.py
+++ b/Lib/fontTools/ttLib/tables/otBase.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, bytesjoin
from .DefaultTable import DefaultTable
import sys
import array
@@ -135,48 +134,38 @@ class OTTableReader(object):
offset = self.offset + offset
return self.__class__(self.data, self.localState, offset, self.tableTag)
- def readUShort(self):
+ def readValue(self, typecode, staticSize):
pos = self.pos
- newpos = pos + 2
- value, = struct.unpack(">H", self.data[pos:newpos])
+ newpos = pos + staticSize
+ value, = struct.unpack(f">{typecode}", self.data[pos:newpos])
self.pos = newpos
return value
- def readUShortArray(self, count):
+ def readUShort(self):
+ return self.readValue("H", staticSize=2)
+
+ def readArray(self, typecode, staticSize, count):
pos = self.pos
- newpos = pos + count * 2
- value = array.array("H", self.data[pos:newpos])
+ newpos = pos + count * staticSize
+ value = array.array(typecode, self.data[pos:newpos])
if sys.byteorder != "big": value.byteswap()
self.pos = newpos
return value
+ def readUShortArray(self, count):
+ return self.readArray("H", staticSize=2, count=count)
+
def readInt8(self):
- pos = self.pos
- newpos = pos + 1
- value, = struct.unpack(">b", self.data[pos:newpos])
- self.pos = newpos
- return value
+ return self.readValue("b", staticSize=1)
def readShort(self):
- pos = self.pos
- newpos = pos + 2
- value, = struct.unpack(">h", self.data[pos:newpos])
- self.pos = newpos
- return value
+ return self.readValue("h", staticSize=2)
def readLong(self):
- pos = self.pos
- newpos = pos + 4
- value, = struct.unpack(">l", self.data[pos:newpos])
- self.pos = newpos
- return value
+ return self.readValue("l", staticSize=4)
def readUInt8(self):
- pos = self.pos
- newpos = pos + 1
- value, = struct.unpack(">B", self.data[pos:newpos])
- self.pos = newpos
- return value
+ return self.readValue("B", staticSize=1)
def readUInt24(self):
pos = self.pos
@@ -186,11 +175,7 @@ class OTTableReader(object):
return value
def readULong(self):
- pos = self.pos
- newpos = pos + 4
- value, = struct.unpack(">L", self.data[pos:newpos])
- self.pos = newpos
- return value
+ return self.readValue("L", staticSize=4)
def readTag(self):
pos = self.pos
@@ -223,14 +208,24 @@ class OTTableWriter(object):
"""Helper class to gather and assemble data for OpenType tables."""
- def __init__(self, localState=None, tableTag=None):
+ def __init__(self, localState=None, tableTag=None, offsetSize=2):
self.items = []
self.pos = None
self.localState = localState
self.tableTag = tableTag
- self.longOffset = False
+ self.offsetSize = offsetSize
self.parent = None
+ # DEPRECATED: 'longOffset' is kept as a property for backward compat with old code.
+ # You should use 'offsetSize' instead (2, 3 or 4 bytes).
+ @property
+ def longOffset(self):
+ return self.offsetSize == 4
+
+ @longOffset.setter
+ def longOffset(self, value):
+ self.offsetSize = 4 if value else 2
+
def __setitem__(self, name, value):
state = self.localState.copy() if self.localState else dict()
state[name] = value
@@ -251,7 +246,7 @@ class OTTableWriter(object):
if hasattr(item, "getCountData"):
l += item.size
elif hasattr(item, "getData"):
- l += 4 if item.longOffset else 2
+ l += item.offsetSize
else:
l = l + len(item)
return l
@@ -265,9 +260,9 @@ class OTTableWriter(object):
item = items[i]
if hasattr(item, "getData"):
- if item.longOffset:
+ if item.offsetSize == 4:
items[i] = packULong(item.pos - pos)
- else:
+ elif item.offsetSize == 2:
try:
items[i] = packUShort(item.pos - pos)
except struct.error:
@@ -275,6 +270,10 @@ class OTTableWriter(object):
overflowErrorRecord = self.getOverflowErrorRecord(item)
raise OTLOffsetOverflowError(overflowErrorRecord)
+ elif item.offsetSize == 3:
+ items[i] = packUInt24(item.pos - pos)
+ else:
+ raise ValueError(item.offsetSize)
return bytesjoin(items)
@@ -289,7 +288,7 @@ class OTTableWriter(object):
def __eq__(self, other):
if type(self) != type(other):
return NotImplemented
- return self.longOffset == other.longOffset and self.items == other.items
+ return self.offsetSize == other.offsetSize and self.items == other.items
def _doneWriting(self, internedTables):
# Convert CountData references to data string items
@@ -410,14 +409,17 @@ class OTTableWriter(object):
# interface for gathering data, as used by table.compile()
- def getSubWriter(self):
- subwriter = self.__class__(self.localState, self.tableTag)
+ def getSubWriter(self, offsetSize=2):
+ subwriter = self.__class__(self.localState, self.tableTag, offsetSize=offsetSize)
subwriter.parent = self # because some subtables have idential values, we discard
# the duplicates under the getAllData method. Hence some
# subtable writers can have more than one parent writer.
# But we just care about first one right now.
return subwriter
+ def writeValue(self, typecode, value):
+ self.items.append(struct.pack(f">{typecode}", value))
+
def writeUShort(self, value):
assert 0 <= value < 0x10000, value
self.items.append(struct.pack(">H", value))
@@ -514,6 +516,8 @@ class CountReference(object):
table[name] = value
else:
assert table[name] == value, (name, table[name], value)
+ def getValue(self):
+ return self.table[self.name]
def getCountData(self):
v = self.table[self.name]
if v is None: v = 0
@@ -530,6 +534,10 @@ def packULong(value):
assert 0 <= value < 0x100000000, value
return struct.pack(">L", value)
+def packUInt24(value):
+ assert 0 <= value < 0x1000000, value
+ return struct.pack(">L", value)[1:]
+
class BaseTable(object):
@@ -646,11 +654,26 @@ class BaseTable(object):
def compile(self, writer, font):
self.ensureDecompiled()
+ # TODO Following hack to be removed by rewriting how FormatSwitching tables
+ # are handled.
+ # https://github.com/fonttools/fonttools/pull/2238#issuecomment-805192631
if hasattr(self, 'preWrite'):
+ deleteFormat = not hasattr(self, 'Format')
table = self.preWrite(font)
+ deleteFormat = deleteFormat and hasattr(self, 'Format')
else:
+ deleteFormat = False
table = self.__dict__.copy()
+ # some count references may have been initialized in a custom preWrite; we set
+ # these in the writer's state beforehand (instead of sequentially) so they will
+ # be propagated to all nested subtables even if the count appears in the current
+ # table only *after* the offset to the subtable that it is counting.
+ for conv in self.getConverters():
+ if conv.isCount and conv.isPropagated:
+ value = table.get(conv.name)
+ if isinstance(value, CountReference):
+ writer[conv.name] = value
if hasattr(self, 'sortCoverageLast'):
writer.sortCoverageLast = 1
@@ -691,8 +714,16 @@ class BaseTable(object):
# table. We will later store it here.
# We add a reference: by the time the data is assembled
# the Count value will be filled in.
- ref = writer.writeCountReference(table, conv.name, conv.staticSize)
- table[conv.name] = None
+ # We ignore the current count value since it will be recomputed,
+ # unless it's a CountReference that was already initialized in a custom preWrite.
+ if isinstance(value, CountReference):
+ ref = value
+ ref.size = conv.staticSize
+ writer.writeData(ref)
+ table[conv.name] = ref.getValue()
+ else:
+ ref = writer.writeCountReference(table, conv.name, conv.staticSize)
+ table[conv.name] = None
if conv.isPropagated:
writer[conv.name] = ref
elif conv.isLookupType:
@@ -715,6 +746,9 @@ class BaseTable(object):
if conv.isPropagated:
writer[conv.name] = value
+ if deleteFormat:
+ del self.Format
+
def readFormat(self, reader):
pass
@@ -804,6 +838,26 @@ class FormatSwitchingBaseTable(BaseTable):
BaseTable.toXML(self, xmlWriter, font, attrs, name)
+class UInt8FormatSwitchingBaseTable(FormatSwitchingBaseTable):
+ def readFormat(self, reader):
+ self.Format = reader.readUInt8()
+
+ def writeFormat(self, writer):
+ writer.writeUInt8(self.Format)
+
+
+formatSwitchingBaseTables = {
+ "uint16": FormatSwitchingBaseTable,
+ "uint8": UInt8FormatSwitchingBaseTable,
+}
+
+def getFormatSwitchingBaseTableClass(formatType):
+ try:
+ return formatSwitchingBaseTables[formatType]
+ except KeyError:
+ raise TypeError(f"Unsupported format type: {formatType!r}")
+
+
#
# Support for ValueRecords
#
diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py
index d1712db8..4af38acd 100644
--- a/Lib/fontTools/ttLib/tables/otConverters.py
+++ b/Lib/fontTools/ttLib/tables/otConverters.py
@@ -1,15 +1,22 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, tobytes, tostr
from fontTools.misc.fixedTools import (
- fixedToFloat as fi2fl, floatToFixed as fl2fi, ensureVersionIsLong as fi2ve,
- versionToFixed as ve2fi)
+ fixedToFloat as fi2fl,
+ floatToFixed as fl2fi,
+ floatToFixedToStr as fl2str,
+ strToFixedToFloat as str2fl,
+ ensureVersionIsLong as fi2ve,
+ versionToFixed as ve2fi,
+)
from fontTools.misc.textTools import pad, safeEval
from fontTools.ttLib import getSearchRange
from .otBase import (CountReference, FormatSwitchingBaseTable,
OTTableReader, OTTableWriter, ValueRecordFactory)
from .otTables import (lookupTypes, AATStateTable, AATState, AATAction,
ContextualMorphAction, LigatureMorphAction,
- InsertionMorphAction, MorxSubtable)
+ InsertionMorphAction, MorxSubtable, VariableFloat,
+ VariableInt, ExtendMode as _ExtendMode,
+ CompositeMode as _CompositeMode)
+from itertools import zip_longest
from functools import partial
import struct
import logging
@@ -52,14 +59,20 @@ def buildConverters(tableSpec, tableNamespace):
converterClass = Struct
else:
converterClass = eval(tp, tableNamespace, converterMapping)
- if tp in ('MortChain', 'MortSubtable', 'MorxChain'):
+
+ conv = converterClass(name, repeat, aux)
+
+ if conv.tableClass:
+ # A "template" such as OffsetTo(AType) knowss the table class already
+ tableClass = conv.tableClass
+ elif tp in ('MortChain', 'MortSubtable', 'MorxChain'):
tableClass = tableNamespace.get(tp)
else:
tableClass = tableNamespace.get(tableName)
- if tableClass is not None:
- conv = converterClass(name, repeat, aux, tableClass=tableClass)
- else:
- conv = converterClass(name, repeat, aux)
+
+ if not conv.tableClass:
+ conv.tableClass = tableClass
+
if name in ["SubTable", "ExtSubTable", "SubStruct"]:
conv.lookupTypes = tableNamespace['lookupTypes']
# also create reverse mapping
@@ -130,7 +143,22 @@ class BaseConverter(object):
self.tableClass = tableClass
self.isCount = name.endswith("Count") or name in ['DesignAxisRecordSize', 'ValueRecordSize']
self.isLookupType = name.endswith("LookupType") or name == "MorphType"
- self.isPropagated = name in ["ClassCount", "Class2Count", "FeatureTag", "SettingsCount", "VarRegionCount", "MappingCount", "RegionAxisCount", 'DesignAxisCount', 'DesignAxisRecordSize', 'AxisValueCount', 'ValueRecordSize', 'AxisCount']
+ self.isPropagated = name in [
+ "ClassCount",
+ "Class2Count",
+ "FeatureTag",
+ "SettingsCount",
+ "VarRegionCount",
+ "MappingCount",
+ "RegionAxisCount",
+ "DesignAxisCount",
+ "DesignAxisRecordSize",
+ "AxisValueCount",
+ "ValueRecordSize",
+ "AxisCount",
+ "BaseGlyphRecordCount",
+ "LayerRecordCount",
+ ]
def readArray(self, reader, font, tableDict, count):
"""Read an array of values from the reader."""
@@ -181,15 +209,22 @@ class BaseConverter(object):
class SimpleValue(BaseConverter):
+ @staticmethod
+ def toString(value):
+ return value
+ @staticmethod
+ def fromString(value):
+ return value
def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.simpletag(name, attrs + [("value", value)])
+ xmlWriter.simpletag(name, attrs + [("value", self.toString(value))])
xmlWriter.newline()
def xmlRead(self, attrs, content, font):
- return attrs["value"]
+ return self.fromString(attrs["value"])
class IntValue(SimpleValue):
- def xmlRead(self, attrs, content, font):
- return int(attrs["value"], 0)
+ @staticmethod
+ def fromString(value):
+ return int(value, 0)
class Long(IntValue):
staticSize = 4
@@ -206,9 +241,9 @@ class ULong(IntValue):
writer.writeULong(value)
class Flags32(ULong):
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.simpletag(name, attrs + [("value", "0x%08X" % value)])
- xmlWriter.newline()
+ @staticmethod
+ def toString(value):
+ return "0x%08X" % value
class Short(IntValue):
staticSize = 2
@@ -267,9 +302,10 @@ class Tag(SimpleValue):
class GlyphID(SimpleValue):
staticSize = 2
+ typecode = "H"
def readArray(self, reader, font, tableDict, count):
glyphOrder = font.getGlyphOrder()
- gids = reader.readUShortArray(count)
+ gids = reader.readArray(self.typecode, self.staticSize, count)
try:
l = [glyphOrder[gid] for gid in gids]
except IndexError:
@@ -277,9 +313,14 @@ class GlyphID(SimpleValue):
l = [font.getGlyphName(gid) for gid in gids]
return l
def read(self, reader, font, tableDict):
- return font.getGlyphName(reader.readUShort())
+ return font.getGlyphName(reader.readValue(self.typecode, self.staticSize))
def write(self, writer, font, tableDict, value, repeatIndex=None):
- writer.writeUShort(font.getGlyphID(value))
+ writer.writeValue(self.typecode, font.getGlyphID(value))
+
+
+class GlyphID32(GlyphID):
+ staticSize = 4
+ typecode = "L"
class NameID(UShort):
@@ -297,10 +338,23 @@ class NameID(UShort):
log.warning("name id %d missing from name table" % value)
xmlWriter.newline()
+class STATFlags(UShort):
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.simpletag(name, attrs + [("value", value)])
+ flags = []
+ if value & 0x01:
+ flags.append("OlderSiblingFontAttribute")
+ if value & 0x02:
+ flags.append("ElidableAxisValueName")
+ if flags:
+ xmlWriter.write(" ")
+ xmlWriter.comment(" ".join(flags))
+ xmlWriter.newline()
class FloatValue(SimpleValue):
- def xmlRead(self, attrs, content, font):
- return float(attrs["value"])
+ @staticmethod
+ def fromString(value):
+ return float(value)
class DeciPoints(FloatValue):
staticSize = 2
@@ -316,6 +370,12 @@ class Fixed(FloatValue):
return fi2fl(reader.readLong(), 16)
def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeLong(fl2fi(value, 16))
+ @staticmethod
+ def fromString(value):
+ return str2fl(value, 16)
+ @staticmethod
+ def toString(value):
+ return fl2str(value, 16)
class F2Dot14(FloatValue):
staticSize = 2
@@ -323,8 +383,14 @@ class F2Dot14(FloatValue):
return fi2fl(reader.readShort(), 14)
def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeShort(fl2fi(value, 14))
+ @staticmethod
+ def fromString(value):
+ return str2fl(value, 14)
+ @staticmethod
+ def toString(value):
+ return fl2str(value, 14)
-class Version(BaseConverter):
+class Version(SimpleValue):
staticSize = 4
def read(self, reader, font, tableDict):
value = reader.readLong()
@@ -334,16 +400,12 @@ class Version(BaseConverter):
value = fi2ve(value)
assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
writer.writeLong(value)
- def xmlRead(self, attrs, content, font):
- value = attrs["value"]
- value = ve2fi(value)
- return value
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- value = fi2ve(value)
- value = "0x%08x" % value
- xmlWriter.simpletag(name, attrs + [("value", value)])
- xmlWriter.newline()
-
+ @staticmethod
+ def fromString(value):
+ return ve2fi(value)
+ @staticmethod
+ def toString(value):
+ return "0x%08x" % value
@staticmethod
def fromFloat(v):
return fl2fi(v, 16)
@@ -362,8 +424,8 @@ class Char64(SimpleValue):
zeroPos = data.find(b"\0")
if zeroPos >= 0:
data = data[:zeroPos]
- s = tounicode(data, encoding="ascii", errors="replace")
- if s != tounicode(data, encoding="ascii", errors="ignore"):
+ s = tostr(data, encoding="ascii", errors="replace")
+ if s != tostr(data, encoding="ascii", errors="ignore"):
log.warning('replaced non-ASCII characters in "%s"' %
s)
return s
@@ -482,17 +544,13 @@ class StructWithLength(Struct):
class Table(Struct):
- longOffset = False
staticSize = 2
def readOffset(self, reader):
return reader.readUShort()
def writeNullOffset(self, writer):
- if self.longOffset:
- writer.writeULong(0)
- else:
- writer.writeUShort(0)
+ writer.writeUShort(0)
def read(self, reader, font, tableDict):
offset = self.readOffset(reader)
@@ -511,8 +569,7 @@ class Table(Struct):
if value is None:
self.writeNullOffset(writer)
else:
- subWriter = writer.getSubWriter()
- subWriter.longOffset = self.longOffset
+ subWriter = writer.getSubWriter(offsetSize=self.staticSize)
subWriter.name = self.name
if repeatIndex is not None:
subWriter.repeatIndex = repeatIndex
@@ -521,12 +578,26 @@ class Table(Struct):
class LTable(Table):
- longOffset = True
staticSize = 4
def readOffset(self, reader):
return reader.readULong()
+ def writeNullOffset(self, writer):
+ writer.writeULong(0)
+
+
+# Table pointed to by a 24-bit, 3-byte long offset
+class Table24(Table):
+
+ staticSize = 3
+
+ def readOffset(self, reader):
+ return reader.readUInt24()
+
+ def writeNullOffset(self, writer):
+ writer.writeUInt24(0)
+
# TODO Clean / merge the SubTable and SubStruct
@@ -862,13 +933,11 @@ class AATLookupWithDataOffset(BaseConverter):
offsetByGlyph[glyph] = offset
# For calculating the offsets to our AATLookup and data table,
# we can use the regular OTTableWriter infrastructure.
- lookupWriter = writer.getSubWriter()
- lookupWriter.longOffset = True
+ lookupWriter = writer.getSubWriter(offsetSize=4)
lookup = AATLookup('DataOffsets', None, None, UShort)
lookup.write(lookupWriter, font, tableDict, offsetByGlyph, None)
- dataWriter = writer.getSubWriter()
- dataWriter.longOffset = True
+ dataWriter = writer.getSubWriter(offsetSize=4)
writer.writeSubTable(lookupWriter)
writer.writeSubTable(dataWriter)
for d in compiledData:
@@ -1209,8 +1278,7 @@ class STXHeader(BaseConverter):
(len(table.PerGlyphLookups), numLookups))
writer = OTTableWriter()
for lookup in table.PerGlyphLookups:
- lookupWriter = writer.getSubWriter()
- lookupWriter.longOffset = True
+ lookupWriter = writer.getSubWriter(offsetSize=4)
self.perGlyphLookup.write(lookupWriter, font,
{}, lookup, None)
writer.writeSubTable(lookupWriter)
@@ -1555,13 +1623,140 @@ class VarDataValue(BaseConverter):
def xmlRead(self, attrs, content, font):
return safeEval(attrs["value"])
+class LookupFlag(UShort):
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.simpletag(name, attrs + [("value", value)])
+ flags = []
+ if value & 0x01: flags.append("rightToLeft")
+ if value & 0x02: flags.append("ignoreBaseGlyphs")
+ if value & 0x04: flags.append("ignoreLigatures")
+ if value & 0x08: flags.append("ignoreMarks")
+ if value & 0x10: flags.append("useMarkFilteringSet")
+ if value & 0xff00: flags.append("markAttachmentType[%i]" % (value >> 8))
+ if flags:
+ xmlWriter.comment(" ".join(flags))
+ xmlWriter.newline()
+
+def _issubclass_namedtuple(x):
+ return (
+ issubclass(x, tuple)
+ and getattr(x, "_fields", None) is not None
+ )
+
+
+class _NamedTupleConverter(BaseConverter):
+ # subclasses must override this
+ tupleClass = NotImplemented
+ # List[SimpleValue]
+ converterClasses = NotImplemented
+
+ def __init__(self, name, repeat, aux, tableClass=None):
+ # we expect all converters to be subclasses of SimpleValue
+ assert all(issubclass(klass, SimpleValue) for klass in self.converterClasses)
+ assert _issubclass_namedtuple(self.tupleClass), repr(self.tupleClass)
+ assert len(self.tupleClass._fields) == len(self.converterClasses)
+ assert tableClass is None # tableClass is unused by SimplValues
+ BaseConverter.__init__(self, name, repeat, aux)
+ self.converters = [
+ klass(name=name, repeat=None, aux=None)
+ for name, klass in zip(self.tupleClass._fields, self.converterClasses)
+ ]
+ self.convertersByName = {conv.name: conv for conv in self.converters}
+ # returned by getRecordSize method
+ self.staticSize = sum(c.staticSize for c in self.converters)
+
+ def read(self, reader, font, tableDict):
+ kwargs = {
+ conv.name: conv.read(reader, font, tableDict)
+ for conv in self.converters
+ }
+ return self.tupleClass(**kwargs)
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ for conv in self.converters:
+ v = getattr(value, conv.name)
+ # repeatIndex is unused for SimpleValues
+ conv.write(writer, font, tableDict, v, repeatIndex=None)
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ assert value is not None
+ defaults = value.__new__.__defaults__ or ()
+ assert len(self.converters) >= len(defaults)
+ values = {}
+ required = object()
+ for conv, default in zip_longest(
+ reversed(self.converters),
+ reversed(defaults),
+ fillvalue=required,
+ ):
+ v = getattr(value, conv.name)
+ if default is required or v != default:
+ values[conv.name] = conv.toString(v)
+ if attrs is None:
+ attrs = []
+ attrs.extend(
+ (conv.name, values[conv.name])
+ for conv in self.converters
+ if conv.name in values
+ )
+ xmlWriter.simpletag(name, attrs)
+ xmlWriter.newline()
+
+ def xmlRead(self, attrs, content, font):
+ converters = self.convertersByName
+ kwargs = {
+ k: converters[k].fromString(v)
+ for k, v in attrs.items()
+ }
+ return self.tupleClass(**kwargs)
+
+
+class VarFixed(_NamedTupleConverter):
+ tupleClass = VariableFloat
+ converterClasses = [Fixed, ULong]
+
+
+class VarF2Dot14(_NamedTupleConverter):
+ tupleClass = VariableFloat
+ converterClasses = [F2Dot14, ULong]
+
+
+class VarInt16(_NamedTupleConverter):
+ tupleClass = VariableInt
+ converterClasses = [Short, ULong]
+
+
+class VarUInt16(_NamedTupleConverter):
+ tupleClass = VariableInt
+ converterClasses = [UShort, ULong]
+
+
+class _UInt8Enum(UInt8):
+ enumClass = NotImplemented
+
+ def read(self, reader, font, tableDict):
+ return self.enumClass(super().read(reader, font, tableDict))
+ @classmethod
+ def fromString(cls, value):
+ return getattr(cls.enumClass, value.upper())
+ @classmethod
+ def toString(cls, value):
+ return cls.enumClass(value).name.lower()
+
+
+class ExtendMode(_UInt8Enum):
+ enumClass = _ExtendMode
+
+
+class CompositeMode(_UInt8Enum):
+ enumClass = _CompositeMode
+
converterMapping = {
# type class
"int8": Int8,
"int16": Short,
"uint8": UInt8,
- "uint8": UInt8,
"uint16": UShort,
"uint24": UInt24,
"uint32": ULong,
@@ -1570,6 +1765,7 @@ converterMapping = {
"Version": Version,
"Tag": Tag,
"GlyphID": GlyphID,
+ "GlyphID32": GlyphID32,
"NameID": NameID,
"DeciPoints": DeciPoints,
"Fixed": Fixed,
@@ -1577,10 +1773,15 @@ converterMapping = {
"struct": Struct,
"Offset": Table,
"LOffset": LTable,
+ "Offset24": Table24,
"ValueRecord": ValueRecord,
"DeltaValue": DeltaValue,
"VarIdxMapValue": VarIdxMapValue,
"VarDataValue": VarDataValue,
+ "LookupFlag": LookupFlag,
+ "ExtendMode": ExtendMode,
+ "CompositeMode": CompositeMode,
+ "STATFlags": STATFlags,
# AAT
"CIDGlyphMap": CIDGlyphMap,
@@ -1596,4 +1797,11 @@ converterMapping = {
"STXHeader": lambda C: partial(STXHeader, tableClass=C),
"OffsetTo": lambda C: partial(Table, tableClass=C),
"LOffsetTo": lambda C: partial(LTable, tableClass=C),
+ "LOffset24To": lambda C: partial(Table24, tableClass=C),
+
+ # Variable types
+ "VarFixed": VarFixed,
+ "VarF2Dot14": VarF2Dot14,
+ "VarInt16": VarInt16,
+ "VarUInt16": VarUInt16,
}
diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py
index d08bcc57..c4294169 100755
--- a/Lib/fontTools/ttLib/tables/otData.py
+++ b/Lib/fontTools/ttLib/tables/otData.py
@@ -1,7 +1,3 @@
-# coding: utf-8
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
otData = [
#
@@ -88,7 +84,7 @@ otData = [
('Lookup', [
('uint16', 'LookupType', None, None, 'Different enumerations for GSUB and GPOS'),
- ('uint16', 'LookupFlag', None, None, 'Lookup qualifiers'),
+ ('LookupFlag', 'LookupFlag', None, None, 'Lookup qualifiers'),
('uint16', 'SubTableCount', None, None, 'Number of SubTables for this lookup'),
('Offset', 'SubTable', 'SubTableCount', 0, 'Array of offsets to SubTables-from beginning of Lookup table'),
('uint16', 'MarkFilteringSet', None, 'LookupFlag & 0x0010', 'If set, indicates that the lookup table structure is followed by a MarkFilteringSet field. The layout engine skips over all mark glyphs not in the mark filtering set indicated.'),
@@ -703,6 +699,7 @@ otData = [
('Version', 'Version', None, None, 'Version of the BASE table-initially 0x00010000'),
('Offset', 'HorizAxis', None, None, 'Offset to horizontal Axis table-from beginning of BASE table-may be NULL'),
('Offset', 'VertAxis', None, None, 'Offset to vertical Axis table-from beginning of BASE table-may be NULL'),
+ ('LOffset', 'VarStore', None, 'Version >= 0x00010001', 'Offset to variation store (may be NULL)'),
]),
('Axis', [
@@ -872,7 +869,7 @@ otData = [
('AxisValueFormat1', [
('uint16', 'Format', None, None, 'Format, = 1'),
('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'),
- ('uint16', 'Flags', None, None, 'Flags.'),
+ ('STATFlags', 'Flags', None, None, 'Flags.'),
('NameID', 'ValueNameID', None, None, ''),
('Fixed', 'Value', None, None, ''),
]),
@@ -880,7 +877,7 @@ otData = [
('AxisValueFormat2', [
('uint16', 'Format', None, None, 'Format, = 2'),
('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'),
- ('uint16', 'Flags', None, None, 'Flags.'),
+ ('STATFlags', 'Flags', None, None, 'Flags.'),
('NameID', 'ValueNameID', None, None, ''),
('Fixed', 'NominalValue', None, None, ''),
('Fixed', 'RangeMinValue', None, None, ''),
@@ -890,7 +887,7 @@ otData = [
('AxisValueFormat3', [
('uint16', 'Format', None, None, 'Format, = 3'),
('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'),
- ('uint16', 'Flags', None, None, 'Flags.'),
+ ('STATFlags', 'Flags', None, None, 'Flags.'),
('NameID', 'ValueNameID', None, None, ''),
('Fixed', 'Value', None, None, ''),
('Fixed', 'LinkedValue', None, None, ''),
@@ -899,7 +896,7 @@ otData = [
('AxisValueFormat4', [
('uint16', 'Format', None, None, 'Format, = 4'),
('uint16', 'AxisCount', None, None, 'The total number of axes contributing to this axis-values combination.'),
- ('uint16', 'Flags', None, None, 'Flags.'),
+ ('STATFlags', 'Flags', None, None, 'Flags.'),
('NameID', 'ValueNameID', None, None, ''),
('struct', 'AxisValueRecord', 'AxisCount', 0, 'Array of AxisValue records that provide the combination of axis values, one for each contributing axis. '),
]),
@@ -1539,4 +1536,275 @@ otData = [
('int16', 'CVTValueArray', 'NumCVTEntries', 0, 'CVT value'),
]),
+ #
+ # COLR
+ #
+
+ ('COLR', [
+ ('uint16', 'Version', None, None, 'Table version number (starts at 0).'),
+ ('uint16', 'BaseGlyphRecordCount', None, None, 'Number of Base Glyph Records.'),
+ ('LOffset', 'BaseGlyphRecordArray', None, None, 'Offset (from beginning of COLR table) to Base Glyph records.'),
+ ('LOffset', 'LayerRecordArray', None, None, 'Offset (from beginning of COLR table) to Layer Records.'),
+ ('uint16', 'LayerRecordCount', None, None, 'Number of Layer Records.'),
+ ('LOffset', 'BaseGlyphV1List', None, 'Version >= 1', 'Offset (from beginning of COLR table) to array of Version-1 Base Glyph records.'),
+ ('LOffset', 'LayerV1List', None, 'Version >= 1', 'Offset (from beginning of COLR table) to LayerV1List.'),
+ ('LOffset', 'VarStore', None, 'Version >= 1', 'Offset to variation store (may be NULL)'),
+ ]),
+
+ ('BaseGlyphRecordArray', [
+ ('BaseGlyphRecord', 'BaseGlyphRecord', 'BaseGlyphRecordCount', 0, 'Base Glyph records.'),
+ ]),
+
+ ('BaseGlyphRecord', [
+ ('GlyphID', 'BaseGlyph', None, None, 'Glyph ID of reference glyph. This glyph is for reference only and is not rendered for color.'),
+ ('uint16', 'FirstLayerIndex', None, None, 'Index (from beginning of the Layer Records) to the layer record. There will be numLayers consecutive entries for this base glyph.'),
+ ('uint16', 'NumLayers', None, None, 'Number of color layers associated with this glyph.'),
+ ]),
+
+ ('LayerRecordArray', [
+ ('LayerRecord', 'LayerRecord', 'LayerRecordCount', 0, 'Layer records.'),
+ ]),
+
+ ('LayerRecord', [
+ ('GlyphID', 'LayerGlyph', None, None, 'Glyph ID of layer glyph (must be in z-order from bottom to top).'),
+ ('uint16', 'PaletteIndex', None, None, 'Index value to use with a selected color palette.'),
+ ]),
+
+ ('BaseGlyphV1List', [
+ ('uint32', 'BaseGlyphCount', None, None, 'Number of Version-1 Base Glyph records'),
+ ('struct', 'BaseGlyphV1Record', 'BaseGlyphCount', 0, 'Array of Version-1 Base Glyph records'),
+ ]),
+
+ ('BaseGlyphV1Record', [
+ ('GlyphID', 'BaseGlyph', None, None, 'Glyph ID of reference glyph.'),
+ ('LOffset', 'Paint', None, None, 'Offset (from beginning of BaseGlyphV1Record) to Paint, typically a PaintColrLayers.'),
+ ]),
+
+ ('LayerV1List', [
+ ('uint32', 'LayerCount', None, None, 'Number of Version-1 Layers'),
+ ('LOffset', 'Paint', 'LayerCount', 0, 'Array of offsets to Paint tables, from the start of the LayerV1List table.'),
+ ]),
+
+ # COLRv1 Affine2x3 uses the same column-major order to serialize a 2D
+ # Affine Transformation as the one used by fontTools.misc.transform.
+ # However, for historical reasons, the labels 'xy' and 'yx' are swapped.
+ # Their fundamental meaning is the same though.
+ # COLRv1 Affine2x3 follows the names found in FreeType and Cairo.
+ # In all case, the second element in the 6-tuple correspond to the
+ # y-part of the x basis vector, and the third to the x-part of the y
+ # basis vector.
+ # See https://github.com/googlefonts/colr-gradients-spec/pull/85
+ ('Affine2x3', [
+ ('Fixed', 'xx', None, None, 'x-part of x basis vector'),
+ ('Fixed', 'yx', None, None, 'y-part of x basis vector'),
+ ('Fixed', 'xy', None, None, 'x-part of y basis vector'),
+ ('Fixed', 'yy', None, None, 'y-part of y basis vector'),
+ ('Fixed', 'dx', None, None, 'Translation in x direction'),
+ ('Fixed', 'dy', None, None, 'Translation in y direction'),
+ ]),
+ ('VarAffine2x3', [
+ ('VarFixed', 'xx', None, None, 'x-part of x basis vector'),
+ ('VarFixed', 'yx', None, None, 'y-part of x basis vector'),
+ ('VarFixed', 'xy', None, None, 'x-part of y basis vector'),
+ ('VarFixed', 'yy', None, None, 'y-part of y basis vector'),
+ ('VarFixed', 'dx', None, None, 'Translation in x direction'),
+ ('VarFixed', 'dy', None, None, 'Translation in y direction'),
+ ]),
+
+ ('ColorIndex', [
+ ('uint16', 'PaletteIndex', None, None, 'Index value to use with a selected color palette.'),
+ ('F2Dot14', 'Alpha', None, None, 'Values outsided [0.,1.] reserved'),
+ ]),
+ ('VarColorIndex', [
+ ('uint16', 'PaletteIndex', None, None, 'Index value to use with a selected color palette.'),
+ ('VarF2Dot14', 'Alpha', None, None, 'Values outsided [0.,1.] reserved'),
+ ]),
+
+ ('ColorStop', [
+ ('F2Dot14', 'StopOffset', None, None, ''),
+ ('ColorIndex', 'Color', None, None, ''),
+ ]),
+ ('VarColorStop', [
+ ('VarF2Dot14', 'StopOffset', None, None, ''),
+ ('VarColorIndex', 'Color', None, None, ''),
+ ]),
+
+ ('ColorLine', [
+ ('ExtendMode', 'Extend', None, None, 'Enum {PAD = 0, REPEAT = 1, REFLECT = 2}'),
+ ('uint16', 'StopCount', None, None, 'Number of Color stops.'),
+ ('ColorStop', 'ColorStop', 'StopCount', 0, 'Array of Color stops.'),
+ ]),
+ ('VarColorLine', [
+ ('ExtendMode', 'Extend', None, None, 'Enum {PAD = 0, REPEAT = 1, REFLECT = 2}'),
+ ('uint16', 'StopCount', None, None, 'Number of Color stops.'),
+ ('VarColorStop', 'ColorStop', 'StopCount', 0, 'Array of Color stops.'),
+ ]),
+
+ # PaintColrLayers
+ ('PaintFormat1', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 1'),
+ ('uint8', 'NumLayers', None, None, 'Number of offsets to Paint to read from LayerV1List.'),
+ ('uint32', 'FirstLayerIndex', None, None, 'Index into LayerV1List.'),
+ ]),
+
+ # PaintSolid
+ ('PaintFormat2', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 2'),
+ ('ColorIndex', 'Color', None, None, 'A solid color paint.'),
+ ]),
+ # PaintVarSolid
+ ('PaintFormat3', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 3'),
+ ('VarColorIndex', 'Color', None, None, 'A solid color paint.'),
+ ]),
+
+ # PaintLinearGradient
+ ('PaintFormat4', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 4'),
+ ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintLinearGradient table) to ColorLine subtable.'),
+ ('int16', 'x0', None, None, ''),
+ ('int16', 'y0', None, None, ''),
+ ('int16', 'x1', None, None, ''),
+ ('int16', 'y1', None, None, ''),
+ ('int16', 'x2', None, None, ''),
+ ('int16', 'y2', None, None, ''),
+ ]),
+ # PaintVarLinearGradient
+ ('PaintFormat5', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 5'),
+ ('LOffset24To(VarColorLine)', 'ColorLine', None, None, 'Offset (from beginning of PaintVarLinearGradient table) to VarColorLine subtable.'),
+ ('VarInt16', 'x0', None, None, ''),
+ ('VarInt16', 'y0', None, None, ''),
+ ('VarInt16', 'x1', None, None, ''),
+ ('VarInt16', 'y1', None, None, ''),
+ ('VarInt16', 'x2', None, None, ''),
+ ('VarInt16', 'y2', None, None, ''),
+ ]),
+
+ # PaintRadialGradient
+ ('PaintFormat6', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 6'),
+ ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintRadialGradient table) to ColorLine subtable.'),
+ ('int16', 'x0', None, None, ''),
+ ('int16', 'y0', None, None, ''),
+ ('uint16', 'r0', None, None, ''),
+ ('int16', 'x1', None, None, ''),
+ ('int16', 'y1', None, None, ''),
+ ('uint16', 'r1', None, None, ''),
+ ]),
+ # PaintVarRadialGradient
+ ('PaintFormat7', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 7'),
+ ('LOffset24To(VarColorLine)', 'ColorLine', None, None, 'Offset (from beginning of PaintVarRadialGradient table) to VarColorLine subtable.'),
+ ('VarInt16', 'x0', None, None, ''),
+ ('VarInt16', 'y0', None, None, ''),
+ ('VarUInt16', 'r0', None, None, ''),
+ ('VarInt16', 'x1', None, None, ''),
+ ('VarInt16', 'y1', None, None, ''),
+ ('VarUInt16', 'r1', None, None, ''),
+ ]),
+
+ # PaintSweepGradient
+ ('PaintFormat8', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 8'),
+ ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintSweepGradient table) to ColorLine subtable.'),
+ ('int16', 'centerX', None, None, 'Center x coordinate.'),
+ ('int16', 'centerY', None, None, 'Center y coordinate.'),
+ ('Fixed', 'startAngle', None, None, 'Start of the angular range of the gradient.'),
+ ('Fixed', 'endAngle', None, None, 'End of the angular range of the gradient.'),
+ ]),
+ # PaintVarSweepGradient
+ ('PaintFormat9', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 9'),
+ ('LOffset24To(VarColorLine)', 'ColorLine', None, None, 'Offset (from beginning of PaintVarSweepGradient table) to VarColorLine subtable.'),
+ ('VarInt16', 'centerX', None, None, 'Center x coordinate.'),
+ ('VarInt16', 'centerY', None, None, 'Center y coordinate.'),
+ ('VarFixed', 'startAngle', None, None, 'Start of the angular range of the gradient.'),
+ ('VarFixed', 'endAngle', None, None, 'End of the angular range of the gradient.'),
+ ]),
+
+ # PaintGlyph
+ ('PaintFormat10', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 10'),
+ ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintGlyph table) to Paint subtable.'),
+ ('GlyphID', 'Glyph', None, None, 'Glyph ID for the source outline.'),
+ ]),
+
+ # PaintColrGlyph
+ ('PaintFormat11', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 11'),
+ ('GlyphID', 'Glyph', None, None, 'Virtual glyph ID for a BaseGlyphV1List base glyph.'),
+ ]),
+
+ # PaintTransform
+ ('PaintFormat12', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 12'),
+ ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTransform table) to Paint subtable.'),
+ ('Affine2x3', 'Transform', None, None, '2x3 matrix for 2D affine transformations.'),
+ ]),
+ # PaintVarTransform
+ ('PaintFormat13', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 13'),
+ ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarTransform table) to Paint subtable.'),
+ ('VarAffine2x3', 'Transform', None, None, '2x3 matrix for 2D affine transformations.'),
+ ]),
+
+ # PaintTranslate
+ ('PaintFormat14', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 14'),
+ ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTranslate table) to Paint subtable.'),
+ ('Fixed', 'dx', None, None, 'Translation in x direction.'),
+ ('Fixed', 'dy', None, None, 'Translation in y direction.'),
+ ]),
+ # PaintVarTranslate
+ ('PaintFormat15', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 15'),
+ ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarTranslate table) to Paint subtable.'),
+ ('VarFixed', 'dx', None, None, 'Translation in x direction.'),
+ ('VarFixed', 'dy', None, None, 'Translation in y direction.'),
+ ]),
+
+ # PaintRotate
+ ('PaintFormat16', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 16'),
+ ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintRotate table) to Paint subtable.'),
+ ('Fixed', 'angle', None, None, ''),
+ ('Fixed', 'centerX', None, None, ''),
+ ('Fixed', 'centerY', None, None, ''),
+ ]),
+ # PaintVarRotate
+ ('PaintFormat17', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 17'),
+ ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarRotate table) to Paint subtable.'),
+ ('VarFixed', 'angle', None, None, ''),
+ ('VarFixed', 'centerX', None, None, ''),
+ ('VarFixed', 'centerY', None, None, ''),
+ ]),
+
+ # PaintSkew
+ ('PaintFormat18', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 18'),
+ ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintSkew table) to Paint subtable.'),
+ ('Fixed', 'xSkewAngle', None, None, ''),
+ ('Fixed', 'ySkewAngle', None, None, ''),
+ ('Fixed', 'centerX', None, None, ''),
+ ('Fixed', 'centerY', None, None, ''),
+ ]),
+ # PaintVarSkew
+ ('PaintFormat19', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 19'),
+ ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarSkew table) to Paint subtable.'),
+ ('VarFixed', 'xSkewAngle', None, None, ''),
+ ('VarFixed', 'ySkewAngle', None, None, ''),
+ ('VarFixed', 'centerX', None, None, ''),
+ ('VarFixed', 'centerY', None, None, ''),
+ ]),
+
+ # PaintComposite
+ ('PaintFormat20', [
+ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 20'),
+ ('LOffset24To(Paint)', 'SourcePaint', None, None, 'Offset (from beginning of PaintComposite table) to source Paint subtable.'),
+ ('CompositeMode', 'CompositeMode', None, None, 'A CompositeMode enumeration value.'),
+ ('LOffset24To(Paint)', 'BackdropPaint', None, None, 'Offset (from beginning of PaintComposite table) to backdrop Paint subtable.'),
+ ]),
]
diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py
index 737e5615..85befb3b 100644
--- a/Lib/fontTools/ttLib/tables/otTables.py
+++ b/Lib/fontTools/ttLib/tables/otTables.py
@@ -5,10 +5,17 @@ OpenType subtables.
Most are constructed upon import from data in otData.py, all are populated with
converter objects from otConverters.py.
"""
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
+from enum import IntEnum
+import itertools
+from collections import namedtuple
+from fontTools.misc.py23 import bytesjoin
+from fontTools.misc.roundTools import otRound
from fontTools.misc.textTools import pad, safeEval
-from .otBase import BaseTable, FormatSwitchingBaseTable, ValueRecord
+from .otBase import (
+ BaseTable, FormatSwitchingBaseTable, ValueRecord, CountReference,
+ getFormatSwitchingBaseTableClass,
+)
+from fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY
import logging
import struct
@@ -551,6 +558,7 @@ class Coverage(FormatSwitchingBaseTable):
else:
self.glyphs = []
log.warning("Unknown Coverage format: %s", self.Format)
+ del self.Format # Don't need this anymore
def preWrite(self, font):
glyphs = getattr(self, "glyphs", None)
@@ -688,6 +696,25 @@ class VarIdxMap(BaseTable):
mapping[glyph] = (outer << 16) | inner
+class VarRegionList(BaseTable):
+
+ def preWrite(self, font):
+ # The OT spec says VarStore.VarRegionList.RegionAxisCount should always
+ # be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule
+ # even when the VarRegionList is empty. We can't treat RegionAxisCount
+ # like a normal propagated count (== len(Region[i].VarRegionAxis)),
+ # otherwise it would default to 0 if VarRegionList is empty.
+ # Thus, we force it to always be equal to fvar.axisCount.
+ # https://github.com/khaledhosny/ots/pull/192
+ fvarTable = font.get("fvar")
+ if fvarTable:
+ self.RegionAxisCount = len(fvarTable.axes)
+ return {
+ **self.__dict__,
+ "RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount")
+ }
+
+
class SingleSubst(FormatSwitchingBaseTable):
def populateDefaults(self, propagator=None):
@@ -713,6 +740,7 @@ class SingleSubst(FormatSwitchingBaseTable):
else:
assert 0, "unknown format: %s" % self.Format
self.mapping = mapping
+ del self.Format # Don't need this anymore
def preWrite(self, font):
mapping = getattr(self, "mapping", None)
@@ -783,6 +811,7 @@ class MultipleSubst(FormatSwitchingBaseTable):
else:
assert 0, "unknown format: %s" % self.Format
self.mapping = mapping
+ del self.Format # Don't need this anymore
def preWrite(self, font):
mapping = getattr(self, "mapping", None)
@@ -901,6 +930,7 @@ class ClassDef(FormatSwitchingBaseTable):
else:
log.warning("Unknown ClassDef format: %s", self.Format)
self.classDefs = classDefs
+ del self.Format # Don't need this anymore
def _getClassRanges(self, font):
classDefs = getattr(self, "classDefs", None)
@@ -989,6 +1019,7 @@ class AlternateSubst(FormatSwitchingBaseTable):
else:
assert 0, "unknown format: %s" % self.Format
self.alternates = alternates
+ del self.Format # Don't need this anymore
def preWrite(self, font):
self.Format = 1
@@ -1059,6 +1090,7 @@ class LigatureSubst(FormatSwitchingBaseTable):
else:
assert 0, "unknown format: %s" % self.Format
self.ligatures = ligatures
+ del self.Format # Don't need this anymore
def preWrite(self, font):
self.Format = 1
@@ -1134,6 +1166,248 @@ class LigatureSubst(FormatSwitchingBaseTable):
ligs.append(lig)
+class COLR(BaseTable):
+
+ def decompile(self, reader, font):
+ # COLRv0 is exceptional in that LayerRecordCount appears *after* the
+ # LayerRecordArray it counts, but the parser logic expects Count fields
+ # to always precede the arrays. Here we work around this by parsing the
+ # LayerRecordCount before the rest of the table, and storing it in
+ # the reader's local state.
+ subReader = reader.getSubReader(offset=0)
+ for conv in self.getConverters():
+ if conv.name != "LayerRecordCount":
+ subReader.advance(conv.staticSize)
+ continue
+ conv = self.getConverterByName("LayerRecordCount")
+ reader[conv.name] = conv.read(subReader, font, tableDict={})
+ break
+ else:
+ raise AssertionError("LayerRecordCount converter not found")
+ return BaseTable.decompile(self, reader, font)
+
+ def preWrite(self, font):
+ # The writer similarly assumes Count values precede the things counted,
+ # thus here we pre-initialize a CountReference; the actual count value
+ # will be set to the lenght of the array by the time this is assembled.
+ self.LayerRecordCount = None
+ return {
+ **self.__dict__,
+ "LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount")
+ }
+
+
+class LookupList(BaseTable):
+ @property
+ def table(self):
+ for l in self.Lookup:
+ for st in l.SubTable:
+ if type(st).__name__.endswith("Subst"):
+ return "GSUB"
+ if type(st).__name__.endswith("Pos"):
+ return "GPOS"
+ raise ValueError
+
+ def toXML2(self, xmlWriter, font):
+ if not font or "Debg" not in font or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data:
+ return super().toXML2(xmlWriter, font)
+ debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table]
+ for conv in self.getConverters():
+ if conv.repeat:
+ value = getattr(self, conv.name, [])
+ for lookupIndex, item in enumerate(value):
+ if str(lookupIndex) in debugData:
+ info = LookupDebugInfo(*debugData[str(lookupIndex)])
+ tag = info.location
+ if info.name:
+ tag = f'{info.name}: {tag}'
+ if info.feature:
+ script,language,feature = info.feature
+ tag = f'{tag} in {feature} ({script}/{language})'
+ xmlWriter.comment(tag)
+ xmlWriter.newline()
+
+ conv.xmlWrite(xmlWriter, font, item, conv.name,
+ [("index", lookupIndex)])
+ else:
+ if conv.aux and not eval(conv.aux, None, vars(self)):
+ continue
+ value = getattr(self, conv.name, None) # TODO Handle defaults instead of defaulting to None!
+ conv.xmlWrite(xmlWriter, font, value, conv.name, [])
+
+class BaseGlyphRecordArray(BaseTable):
+
+ def preWrite(self, font):
+ self.BaseGlyphRecord = sorted(
+ self.BaseGlyphRecord,
+ key=lambda rec: font.getGlyphID(rec.BaseGlyph)
+ )
+ return self.__dict__.copy()
+
+
+class BaseGlyphV1List(BaseTable):
+
+ def preWrite(self, font):
+ self.BaseGlyphV1Record = sorted(
+ self.BaseGlyphV1Record,
+ key=lambda rec: font.getGlyphID(rec.BaseGlyph)
+ )
+ return self.__dict__.copy()
+
+
+
+class VariableValue(namedtuple("VariableValue", ["value", "varIdx"])):
+ __slots__ = ()
+
+ _value_mapper = None
+
+ def __new__(cls, value, varIdx=0):
+ return super().__new__(
+ cls,
+ cls._value_mapper(value) if cls._value_mapper else value,
+ varIdx
+ )
+
+ @classmethod
+ def _make(cls, iterable):
+ if cls._value_mapper:
+ it = iter(iterable)
+ try:
+ value = next(it)
+ except StopIteration:
+ pass
+ else:
+ value = cls._value_mapper(value)
+ iterable = itertools.chain((value,), it)
+ return super()._make(iterable)
+
+
+class VariableFloat(VariableValue):
+ __slots__ = ()
+ _value_mapper = float
+
+
+class VariableInt(VariableValue):
+ __slots__ = ()
+ _value_mapper = otRound
+
+
+class ExtendMode(IntEnum):
+ PAD = 0
+ REPEAT = 1
+ REFLECT = 2
+
+
+# Porter-Duff modes for COLRv1 PaintComposite:
+# https://github.com/googlefonts/colr-gradients-spec/tree/off_sub_1#compositemode-enumeration
+class CompositeMode(IntEnum):
+ CLEAR = 0
+ SRC = 1
+ DEST = 2
+ SRC_OVER = 3
+ DEST_OVER = 4
+ SRC_IN = 5
+ DEST_IN = 6
+ SRC_OUT = 7
+ DEST_OUT = 8
+ SRC_ATOP = 9
+ DEST_ATOP = 10
+ XOR = 11
+ SCREEN = 12
+ OVERLAY = 13
+ DARKEN = 14
+ LIGHTEN = 15
+ COLOR_DODGE = 16
+ COLOR_BURN = 17
+ HARD_LIGHT = 18
+ SOFT_LIGHT = 19
+ DIFFERENCE = 20
+ EXCLUSION = 21
+ MULTIPLY = 22
+ HSL_HUE = 23
+ HSL_SATURATION = 24
+ HSL_COLOR = 25
+ HSL_LUMINOSITY = 26
+
+
+class PaintFormat(IntEnum):
+ PaintColrLayers = 1
+ PaintSolid = 2
+ PaintVarSolid = 3,
+ PaintLinearGradient = 4
+ PaintVarLinearGradient = 5
+ PaintRadialGradient = 6
+ PaintVarRadialGradient = 7
+ PaintSweepGradient = 8
+ PaintVarSweepGradient = 9
+ PaintGlyph = 10
+ PaintColrGlyph = 11
+ PaintTransform = 12
+ PaintVarTransform = 13
+ PaintTranslate = 14
+ PaintVarTranslate = 15
+ PaintRotate = 16
+ PaintVarRotate = 17
+ PaintSkew = 18
+ PaintVarSkew = 19
+ PaintComposite = 20
+
+
+class Paint(getFormatSwitchingBaseTableClass("uint8")):
+
+ def getFormatName(self):
+ try:
+ return PaintFormat(self.Format).name
+ except ValueError:
+ raise NotImplementedError(f"Unknown Paint format: {self.Format}")
+
+ def toXML(self, xmlWriter, font, attrs=None, name=None):
+ tableName = name if name else self.__class__.__name__
+ if attrs is None:
+ attrs = []
+ attrs.append(("Format", self.Format))
+ xmlWriter.begintag(tableName, attrs)
+ xmlWriter.comment(self.getFormatName())
+ xmlWriter.newline()
+ self.toXML2(xmlWriter, font)
+ xmlWriter.endtag(tableName)
+ xmlWriter.newline()
+
+ def getChildren(self, colr):
+ if self.Format == PaintFormat.PaintColrLayers:
+ return colr.LayerV1List.Paint[
+ self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers
+ ]
+
+ if self.Format == PaintFormat.PaintColrGlyph:
+ for record in colr.BaseGlyphV1List.BaseGlyphV1Record:
+ if record.BaseGlyph == self.Glyph:
+ return [record.Paint]
+ else:
+ raise KeyError(f"{self.Glyph!r} not in colr.BaseGlyphV1List")
+
+ children = []
+ for conv in self.getConverters():
+ if conv.tableClass is not None and issubclass(conv.tableClass, type(self)):
+ children.append(getattr(self, conv.name))
+
+ return children
+
+ def traverse(self, colr: COLR, callback):
+ """Depth-first traversal of graph rooted at self, callback on each node."""
+ if not callable(callback):
+ raise TypeError("callback must be callable")
+ stack = [self]
+ visited = set()
+ while stack:
+ current = stack.pop()
+ if id(current) in visited:
+ continue
+ callback(current)
+ visited.add(id(current))
+ stack.extend(reversed(current.getChildren(colr)))
+
+
# For each subtable format there is a class. However, we don't really distinguish
# between "field name" and "format name": often these are the same. Yet there's
# a whole bunch of fields with different names. The following dict is a mapping
@@ -1227,6 +1501,32 @@ def fixLookupOverFlows(ttf, overflowRecord):
ok = 1
return ok
+def splitMultipleSubst(oldSubTable, newSubTable, overflowRecord):
+ ok = 1
+ newSubTable.Format = oldSubTable.Format
+ oldMapping = sorted(oldSubTable.mapping.items())
+ oldLen = len(oldMapping)
+
+ if overflowRecord.itemName in ['Coverage', 'RangeRecord']:
+ # Coverage table is written last. Overflow is to or within the
+ # the coverage table. We will just cut the subtable in half.
+ newLen = oldLen // 2
+
+ elif overflowRecord.itemName == 'Sequence':
+ # We just need to back up by two items from the overflowed
+ # Sequence index to make sure the offset to the Coverage table
+ # doesn't overflow.
+ newLen = overflowRecord.itemIndex - 1
+
+ newSubTable.mapping = {}
+ for i in range(newLen, oldLen):
+ item = oldMapping[i]
+ key = item[0]
+ newSubTable.mapping[key] = item[1]
+ del oldSubTable.mapping[key]
+
+ return ok
+
def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord):
ok = 1
newSubTable.Format = oldSubTable.Format
@@ -1371,6 +1671,7 @@ def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord):
oldMarkCoverage.append(glyphName)
oldMarkRecords.append(markRecord)
else:
+ markRecord.Class -= oldClassCount
newMarkCoverage.append(glyphName)
newMarkRecords.append(markRecord)
@@ -1386,7 +1687,6 @@ def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord):
oldSubTable.MarkCoverage.glyphs = oldMarkCoverage
newSubTable.MarkCoverage = oldSubTable.MarkCoverage.__class__()
- newSubTable.MarkCoverage.Format = oldSubTable.MarkCoverage.Format
newSubTable.MarkCoverage.glyphs = newMarkCoverage
# share the same BaseCoverage in both halves
@@ -1414,7 +1714,7 @@ def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord):
splitTable = { 'GSUB': {
# 1: splitSingleSubst,
-# 2: splitMultipleSubst,
+ 2: splitMultipleSubst,
3: splitAlternateSubst,
4: splitLigatureSubst,
# 5: splitContextSubst,
@@ -1506,7 +1806,11 @@ def _buildClasses():
if m:
# XxxFormatN subtable, we only add the "base" table
name = m.group(1)
- baseClass = FormatSwitchingBaseTable
+ # the first row of a format-switching otData table describes the Format;
+ # the first column defines the type of the Format field.
+ # Currently this can be either 'uint16' or 'uint8'.
+ formatType = table[0][0]
+ baseClass = getFormatSwitchingBaseTableClass(formatType)
if name not in namespace:
# the class doesn't exist yet, so the base implementation is used.
cls = type(name, (baseClass,), {})
diff --git a/Lib/fontTools/ttLib/tables/sbixGlyph.py b/Lib/fontTools/ttLib/tables/sbixGlyph.py
index c0539086..fe29c090 100644
--- a/Lib/fontTools/ttLib/tables/sbixGlyph.py
+++ b/Lib/fontTools/ttLib/tables/sbixGlyph.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import readHex, safeEval
import struct
diff --git a/Lib/fontTools/ttLib/tables/sbixStrike.py b/Lib/fontTools/ttLib/tables/sbixStrike.py
index 6c1ac774..b367a99f 100644
--- a/Lib/fontTools/ttLib/tables/sbixStrike.py
+++ b/Lib/fontTools/ttLib/tables/sbixStrike.py
@@ -1,8 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
-from fontTools.misc.textTools import readHex
-from .sbixGlyph import *
+from fontTools.misc.textTools import safeEval
+from .sbixGlyph import Glyph
import struct
sbixStrikeHeaderFormat = """
diff --git a/Lib/fontTools/ttLib/tables/ttProgram.py b/Lib/fontTools/ttLib/tables/ttProgram.py
index 7ffb37a0..a1dfa3c5 100644
--- a/Lib/fontTools/ttLib/tables/ttProgram.py
+++ b/Lib/fontTools/ttLib/tables/ttProgram.py
@@ -1,9 +1,9 @@
"""ttLib.tables.ttProgram.py -- Assembler/disassembler for TrueType bytecode programs."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import strjoin
from fontTools.misc.textTools import num2binary, binary2num, readHex
import array
+from io import StringIO
import re
import logging
@@ -223,7 +223,7 @@ class Program(object):
def getBytecode(self):
if not hasattr(self, "bytecode"):
self._assemble()
- return self.bytecode.tostring()
+ return self.bytecode.tobytes()
def getAssembly(self, preserve=True):
if not hasattr(self, "assembly"):
diff --git a/Lib/fontTools/ttLib/ttCollection.py b/Lib/fontTools/ttLib/ttCollection.py
index 2507fe30..3db4c8cd 100644
--- a/Lib/fontTools/ttLib/ttCollection.py
+++ b/Lib/fontTools/ttLib/ttCollection.py
@@ -1,7 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib.ttFont import TTFont
from fontTools.ttLib.sfnt import readTTCHeader, writeTTCHeader
+from io import BytesIO
import struct
import logging
@@ -36,6 +35,16 @@ class TTCollection(object):
for i in range(header.numFonts):
font = TTFont(file, fontNumber=i, _tableCache=tableCache, **kwargs)
fonts.append(font)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
+
+ def close(self):
+ for font in self.fonts:
+ font.close()
def save(self, file, shareTables=True):
"""Save the font to disk. Similarly to the constructor,
@@ -95,7 +104,7 @@ class TTCollection(object):
return self.fonts[item]
def __setitem__(self, item, value):
- self.fonts[item] = values
+ self.fonts[item] = value
def __delitem__(self, item):
return self.fonts[item]
diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py
index dca0b515..41a48751 100644
--- a/Lib/fontTools/ttLib/ttFont.py
+++ b/Lib/fontTools/ttLib/ttFont.py
@@ -1,12 +1,12 @@
-from __future__ import print_function, division, absolute_import
from fontTools.misc import xmlWriter
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, byteord, tostr
from fontTools.misc.loggingTools import deprecateArgument
from fontTools.ttLib import TTLibError
from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter
+from io import BytesIO, StringIO
import os
import logging
-import itertools
+import traceback
log = logging.getLogger(__name__)
@@ -19,7 +19,7 @@ class TTFont(object):
"""
def __init__(self, file=None, res_name_or_index=None,
- sfntVersion="\000\001\000\000", flavor=None, checkChecksums=False,
+ sfntVersion="\000\001\000\000", flavor=None, checkChecksums=0,
verbose=None, recalcBBoxes=True, allowVID=False, ignoreDecompileErrors=False,
recalcTimestamp=True, fontNumber=-1, lazy=None, quiet=None,
_tableCache=None):
@@ -162,22 +162,17 @@ class TTFont(object):
if self.lazy and self.reader.file.name == file:
raise TTLibError(
"Can't overwrite TTFont when 'lazy' attribute is True")
- closeStream = True
- file = open(file, "wb")
+ createStream = True
else:
# assume "file" is a writable file object
- closeStream = False
+ createStream = False
tmp = BytesIO()
writer_reordersTables = self._save(tmp)
- if (reorderTables is None or writer_reordersTables or
+ if not (reorderTables is None or writer_reordersTables or
(reorderTables is False and self.reader is None)):
- # don't reorder tables and save as is
- file.write(tmp.getvalue())
- tmp.close()
- else:
if reorderTables is False:
# sort tables using the original font's order
tableOrder = list(self.reader.keys())
@@ -187,12 +182,17 @@ class TTFont(object):
tmp.flush()
tmp2 = BytesIO()
reorderFontTables(tmp, tmp2, tableOrder)
- file.write(tmp2.getvalue())
tmp.close()
- tmp2.close()
+ tmp = tmp2
- if closeStream:
- file.close()
+ if createStream:
+ # "file" is a path
+ with open(file, "wb") as file:
+ file.write(tmp.getvalue())
+ else:
+ file.write(tmp.getvalue())
+
+ tmp.close()
def _save(self, file, tableCache=None):
"""Internal function, to be shared by save() and TTCollection.save()"""
@@ -369,45 +369,46 @@ class TTFont(object):
def __getitem__(self, tag):
tag = Tag(tag)
- try:
- return self.tables[tag]
- except KeyError:
+ table = self.tables.get(tag)
+ if table is None:
if tag == "GlyphOrder":
table = GlyphOrder(tag)
self.tables[tag] = table
- return table
- if self.reader is not None:
- import traceback
- log.debug("Reading '%s' table from disk", tag)
- data = self.reader[tag]
- if self._tableCache is not None:
- table = self._tableCache.get((Tag(tag), data))
- if table is not None:
- return table
- tableClass = getTableClass(tag)
- table = tableClass(tag)
- self.tables[tag] = table
- log.debug("Decompiling '%s' table", tag)
- try:
- table.decompile(data, self)
- except:
- if not self.ignoreDecompileErrors:
- raise
- # fall back to DefaultTable, retaining the binary table data
- log.exception(
- "An exception occurred during the decompilation of the '%s' table", tag)
- from .tables.DefaultTable import DefaultTable
- file = StringIO()
- traceback.print_exc(file=file)
- table = DefaultTable(tag)
- table.ERROR = file.getvalue()
- self.tables[tag] = table
- table.decompile(data, self)
- if self._tableCache is not None:
- self._tableCache[(Tag(tag), data)] = table
- return table
+ elif self.reader is not None:
+ table = self._readTable(tag)
else:
raise KeyError("'%s' table not found" % tag)
+ return table
+
+ def _readTable(self, tag):
+ log.debug("Reading '%s' table from disk", tag)
+ data = self.reader[tag]
+ if self._tableCache is not None:
+ table = self._tableCache.get((tag, data))
+ if table is not None:
+ return table
+ tableClass = getTableClass(tag)
+ table = tableClass(tag)
+ self.tables[tag] = table
+ log.debug("Decompiling '%s' table", tag)
+ try:
+ table.decompile(data, self)
+ except Exception:
+ if not self.ignoreDecompileErrors:
+ raise
+ # fall back to DefaultTable, retaining the binary table data
+ log.exception(
+ "An exception occurred during the decompilation of the '%s' table", tag)
+ from .tables.DefaultTable import DefaultTable
+ file = StringIO()
+ traceback.print_exc(file=file)
+ table = DefaultTable(tag)
+ table.ERROR = file.getvalue()
+ self.tables[tag] = table
+ table.decompile(data, self)
+ if self._tableCache is not None:
+ self._tableCache[(tag, data)] = table
+ return table
def __setitem__(self, tag, table):
self.tables[Tag(tag)] = table
@@ -637,7 +638,7 @@ class TTFont(object):
log.debug("reusing '%s' table", tag)
writer.setEntry(tag, entry)
return
- log.debug("writing '%s' table to disk", tag)
+ log.debug("Writing '%s' table to disk", tag)
writer[tag] = tabledata
if tableCache is not None:
tableCache[(Tag(tag), tabledata)] = writer[tag]
@@ -647,7 +648,7 @@ class TTFont(object):
"""
tag = Tag(tag)
if self.isLoaded(tag):
- log.debug("compiling '%s' table", tag)
+ log.debug("Compiling '%s' table", tag)
return self.tables[tag].compile(self)
elif self.reader and tag in self.reader:
log.debug("Reading '%s' table from disk", tag)
@@ -701,6 +702,13 @@ class _TTGlyphSet(object):
"""
def __init__(self, ttFont, glyphs, glyphType):
+ """Construct a new glyphset.
+
+ Args:
+ font (TTFont): The font object (used to get metrics).
+ glyphs (dict): A dictionary mapping glyph names to ``_TTGlyph`` objects.
+ glyphType (class): Either ``_TTGlyphCFF`` or ``_TTGlyphGlyf``.
+ """
self._glyphs = glyphs
self._hmtx = ttFont['hmtx']
self._vmtx = ttFont['vmtx'] if 'vmtx' in ttFont else None
@@ -741,6 +749,13 @@ class _TTGlyph(object):
"""
def __init__(self, glyphset, glyph, horizontalMetrics, verticalMetrics=None):
+ """Construct a new _TTGlyph.
+
+ Args:
+ glyphset (_TTGlyphSet): A glyphset object used to resolve components.
+ glyph (ttLib.tables._g_l_y_f.Glyph): The glyph object.
+ horizontalMetrics (int, int): The glyph's width and left sidebearing.
+ """
self._glyphset = glyphset
self._glyph = glyph
self.width, self.lsb = horizontalMetrics
@@ -750,7 +765,7 @@ class _TTGlyph(object):
self.height, self.tsb = None, None
def draw(self, pen):
- """Draw the glyph onto Pen. See fontTools.pens.basePen for details
+ """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
how that works.
"""
self._glyph.draw(pen)
@@ -831,10 +846,48 @@ def getTableModule(tag):
return getattr(tables, pyTag)
-def getTableClass(tag):
- """Fetch the packer/unpacker class for a table.
- Return None when no class is found.
+# Registry for custom table packer/unpacker classes. Keys are table
+# tags, values are (moduleName, className) tuples.
+# See registerCustomTableClass() and getCustomTableClass()
+_customTableRegistry = {}
+
+
+def registerCustomTableClass(tag, moduleName, className=None):
+ """Register a custom packer/unpacker class for a table.
+ The 'moduleName' must be an importable module. If no 'className'
+ is given, it is derived from the tag, for example it will be
+ table_C_U_S_T_ for a 'CUST' tag.
+
+ The registered table class should be a subclass of
+ fontTools.ttLib.tables.DefaultTable.DefaultTable
"""
+ if className is None:
+ className = "table_" + tagToIdentifier(tag)
+ _customTableRegistry[tag] = (moduleName, className)
+
+
+def unregisterCustomTableClass(tag):
+ """Unregister the custom packer/unpacker class for a table."""
+ del _customTableRegistry[tag]
+
+
+def getCustomTableClass(tag):
+ """Return the custom table class for tag, if one has been registered
+ with 'registerCustomTableClass()'. Else return None.
+ """
+ if tag not in _customTableRegistry:
+ return None
+ import importlib
+ moduleName, className = _customTableRegistry[tag]
+ module = importlib.import_module(moduleName)
+ return getattr(module, className)
+
+
+def getTableClass(tag):
+ """Fetch the packer/unpacker class for a table."""
+ tableClass = getCustomTableClass(tag)
+ if tableClass is not None:
+ return tableClass
module = getTableModule(tag)
if module is None:
from .tables.DefaultTable import DefaultTable
diff --git a/Lib/fontTools/ttLib/woff2.py b/Lib/fontTools/ttLib/woff2.py
index 56b119a7..cc58afa5 100644
--- a/Lib/fontTools/ttLib/woff2.py
+++ b/Lib/fontTools/ttLib/woff2.py
@@ -1,5 +1,5 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, bytechr, byteord, bytesjoin
+from io import BytesIO
import sys
import array
import struct
@@ -12,7 +12,7 @@ from fontTools.ttLib import (TTFont, TTLibError, getTableModule, getTableClass,
from fontTools.ttLib.sfnt import (SFNTReader, SFNTWriter, DirectoryEntry,
WOFFFlavorData, sfntDirectoryFormat, sfntDirectorySize, SFNTDirectoryEntry,
sfntDirectoryEntrySize, calcChecksum)
-from fontTools.ttLib.tables import ttProgram
+from fontTools.ttLib.tables import ttProgram, _g_l_y_f
import logging
@@ -20,7 +20,10 @@ log = logging.getLogger("fontTools.ttLib.woff2")
haveBrotli = False
try:
- import brotli
+ try:
+ import brotlicffi as brotli
+ except ImportError:
+ import brotli
haveBrotli = True
except ImportError:
pass
@@ -30,7 +33,7 @@ class WOFF2Reader(SFNTReader):
flavor = "woff2"
- def __init__(self, file, checkChecksums=1, fontNumber=-1):
+ def __init__(self, file, checkChecksums=0, fontNumber=-1):
if not haveBrotli:
log.error(
'The WOFF2 decoder requires the Brotli Python extension, available at: '
@@ -227,7 +230,11 @@ class WOFF2Writer(SFNTWriter):
# See:
# https://github.com/khaledhosny/ots/issues/60
# https://github.com/google/woff2/issues/15
- if isTrueType and "glyf" in self.flavorData.transformedTables:
+ if (
+ isTrueType
+ and "glyf" in self.flavorData.transformedTables
+ and "glyf" in self.tables
+ ):
self._normaliseGlyfAndLoca(padding=4)
self._setHeadTransformFlag()
@@ -652,7 +659,7 @@ class WOFF2LocaTable(getTableClass('loca')):
else:
locations = array.array("I", self.locations)
if sys.byteorder != "big": locations.byteswap()
- data = locations.tostring()
+ data = locations.tobytes()
else:
# use the most compact indexFormat given the current glyph offsets
data = super(WOFF2LocaTable, self).compile(ttFont)
@@ -734,7 +741,7 @@ class WOFF2GlyfTable(getTableClass('glyf')):
for glyphID in range(self.numGlyphs):
self._encodeGlyph(glyphID)
- self.bboxStream = self.bboxBitmap.tostring() + self.bboxStream
+ self.bboxStream = self.bboxBitmap.tobytes() + self.bboxStream
for stream in self.subStreams:
setattr(self, stream + 'Size', len(getattr(self, stream)))
self.version = 0
@@ -928,7 +935,7 @@ class WOFF2GlyfTable(getTableClass('glyf')):
flags = array.array('B')
triplets = array.array('B')
for i in range(len(coordinates)):
- onCurve = glyph.flags[i]
+ onCurve = glyph.flags[i] & _g_l_y_f.flagOnCurve
x, y = coordinates[i]
absX = abs(x)
absY = abs(y)
@@ -962,8 +969,8 @@ class WOFF2GlyfTable(getTableClass('glyf')):
triplets.append(absY >> 8)
triplets.append(absY & 0xff)
- self.flagStream += flags.tostring()
- self.glyphStream += triplets.tostring()
+ self.flagStream += flags.tobytes()
+ self.glyphStream += triplets.tobytes()
class WOFF2HmtxTable(getTableClass("hmtx")):
@@ -1094,7 +1101,7 @@ class WOFF2HmtxTable(getTableClass("hmtx")):
)
if sys.byteorder != "big":
advanceWidthArray.byteswap()
- data += advanceWidthArray.tostring()
+ data += advanceWidthArray.tobytes()
if hasLsbArray:
lsbArray = array.array(
@@ -1107,7 +1114,7 @@ class WOFF2HmtxTable(getTableClass("hmtx")):
)
if sys.byteorder != "big":
lsbArray.byteswap()
- data += lsbArray.tostring()
+ data += lsbArray.tobytes()
if hasLeftSideBearingArray:
leftSideBearingArray = array.array(
@@ -1119,7 +1126,7 @@ class WOFF2HmtxTable(getTableClass("hmtx")):
)
if sys.byteorder != "big":
leftSideBearingArray.byteswap()
- data += leftSideBearingArray.tostring()
+ data += leftSideBearingArray.tobytes()
return data
@@ -1165,26 +1172,8 @@ class WOFF2FlavorData(WOFFFlavorData):
raise ValueError(
"'glyf' and 'loca' must be transformed (or not) together"
)
-
- self.majorVersion = None
- self.minorVersion = None
- self.metaData = None
- self.privData = None
+ super(WOFF2FlavorData, self).__init__(reader=reader)
if reader:
- self.majorVersion = reader.majorVersion
- self.minorVersion = reader.minorVersion
- if reader.metaLength:
- reader.file.seek(reader.metaOffset)
- rawData = reader.file.read(reader.metaLength)
- assert len(rawData) == reader.metaLength
- metaData = brotli.decompress(rawData)
- assert len(metaData) == reader.metaOrigLength
- self.metaData = metaData
- if reader.privLength:
- reader.file.seek(reader.privOffset)
- privData = reader.file.read(reader.privLength)
- assert len(privData) == reader.privLength
- self.privData = privData
transformedTables = [
tag
for tag, entry in reader.tables.items()
@@ -1203,6 +1192,9 @@ class WOFF2FlavorData(WOFFFlavorData):
self.transformedTables = set(transformedTables)
+ def _decompress(self, rawData):
+ return brotli.decompress(rawData)
+
def unpackBase128(data):
r""" Read one to five bytes from UIntBase128-encoded input string, and return
@@ -1402,10 +1394,22 @@ def decompress(input_file, output_file):
def main(args=None):
+ """Compress and decompress WOFF2 fonts"""
import argparse
from fontTools import configLogger
from fontTools.ttx import makeOutputFileName
+ class _HelpAction(argparse._HelpAction):
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ subparsers_actions = [
+ action for action in parser._actions
+ if isinstance(action, argparse._SubParsersAction)]
+ for subparsers_action in subparsers_actions:
+ for choice, subparser in subparsers_action.choices.items():
+ print(subparser.format_help())
+ parser.exit()
+
class _NoGlyfTransformAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
namespace.transform_tables.difference_update({"glyf", "loca"})
@@ -1416,12 +1420,18 @@ def main(args=None):
parser = argparse.ArgumentParser(
prog="fonttools ttLib.woff2",
- description="Compress and decompress WOFF2 fonts",
+ description=main.__doc__,
+ add_help = False
)
+ parser.add_argument('-h', '--help', action=_HelpAction,
+ help='show this help message and exit')
+
parser_group = parser.add_subparsers(title="sub-commands")
- parser_compress = parser_group.add_parser("compress")
- parser_decompress = parser_group.add_parser("decompress")
+ parser_compress = parser_group.add_parser("compress",
+ description = "Compress a TTF or OTF font to WOFF2")
+ parser_decompress = parser_group.add_parser("decompress",
+ description = "Decompress a WOFF2 font to OTF")
for subparser in (parser_compress, parser_decompress):
group = subparser.add_mutually_exclusive_group(required=False)
diff --git a/Lib/fontTools/ttx.py b/Lib/fontTools/ttx.py
index 8f598f8a..2eed0c5c 100644
--- a/Lib/fontTools/ttx.py
+++ b/Lib/fontTools/ttx.py
@@ -86,8 +86,7 @@ usage: ttx [options] inputfile1 [... inputfileN]
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, tostr
from fontTools.ttLib import TTFont, TTLibError
from fontTools.misc.macCreatorType import getMacCreatorAndType
from fontTools.unicode import setUnicodeData
@@ -385,6 +384,7 @@ def waitForKeyPress():
def main(args=None):
+ """Convert OpenType fonts to XML and back"""
from fontTools import configLogger
if args is None:
diff --git a/Lib/fontTools/ufoLib/__init__.py b/Lib/fontTools/ufoLib/__init__.py
index 4a1fa864..e846d085 100755
--- a/Lib/fontTools/ufoLib/__init__.py
+++ b/Lib/fontTools/ufoLib/__init__.py
@@ -1,7 +1,6 @@
-from __future__ import absolute_import, unicode_literals
-import sys
import os
from copy import deepcopy
+from os import fsdecode
import logging
import zipfile
import enum
@@ -15,13 +14,12 @@ import fs.osfs
import fs.zipfs
import fs.tempfs
import fs.tools
-from fontTools.misc.py23 import basestring, unicode, tounicode
from fontTools.misc import plistlib
from fontTools.ufoLib.validators import *
from fontTools.ufoLib.filenames import userNameToFileName
from fontTools.ufoLib.converters import convertUFO1OrUFO2KerningToUFO3Kerning
from fontTools.ufoLib.errors import UFOLibError
-from fontTools.ufoLib.utils import datetimeAsTimestamp, fsdecode, numberTypes
+from fontTools.ufoLib.utils import numberTypes, _VersionTupleEnumMixin
"""
A library for importing .ufo files and their descendants.
@@ -94,7 +92,11 @@ LAYERINFO_FILENAME = "layerinfo.plist"
DEFAULT_LAYER_NAME = "public.default"
-supportedUFOFormatVersions = [1, 2, 3]
+
+class UFOFormatVersion(tuple, _VersionTupleEnumMixin, enum.Enum):
+ FORMAT_1_0 = (1, 0)
+ FORMAT_2_0 = (2, 0)
+ FORMAT_3_0 = (3, 0)
class UFOFileStructure(enum.Enum):
@@ -107,7 +109,7 @@ class UFOFileStructure(enum.Enum):
# --------------
-class _UFOBaseIO(object):
+class _UFOBaseIO:
def getFileModificationTime(self, path):
"""
@@ -121,7 +123,7 @@ class _UFOBaseIO(object):
except (fs.errors.MissingInfoNamespace, fs.errors.ResourceNotFound):
return None
else:
- return datetimeAsTimestamp(dt)
+ return dt.timestamp()
def _getPlist(self, fileName, default=None):
"""
@@ -147,7 +149,7 @@ class _UFOBaseIO(object):
except Exception as e:
# TODO(anthrotype): try to narrow this down a little
raise UFOLibError(
- "'%s' could not be read on %s: %s" % (fileName, self.fs, e)
+ f"'{fileName}' could not be read on {self.fs}: {e}"
)
def _writePlist(self, fileName, obj):
@@ -205,7 +207,7 @@ class UFOReader(_UFOBaseIO):
if hasattr(path, "__fspath__"): # support os.PathLike objects
path = path.__fspath__()
- if isinstance(path, basestring):
+ if isinstance(path, str):
structure = _sniffFileStructure(path)
try:
if structure is UFOFileStructure.ZIP:
@@ -213,7 +215,7 @@ class UFOReader(_UFOBaseIO):
else:
parentFS = fs.osfs.OSFS(path)
except fs.errors.CreateFailed as e:
- raise UFOLibError("unable to open '%s': %s" % (path, e))
+ raise UFOLibError(f"unable to open '{path}': {e}")
if structure is UFOFileStructure.ZIP:
# .ufoz zip files must contain a single root directory, with arbitrary
@@ -252,7 +254,7 @@ class UFOReader(_UFOBaseIO):
path = filesystem.getsyspath("/")
except fs.errors.NoSysPath:
# network or in-memory FS may not map to the local one
- path = unicode(filesystem)
+ path = str(filesystem)
# when user passed an already initialized fs instance, it is her
# responsibility to close it, thus UFOReader.close/__exit__ are no-op
self._shouldClose = False
@@ -265,9 +267,14 @@ class UFOReader(_UFOBaseIO):
)
self._path = fsdecode(path)
self._validate = validate
- self.readMetaInfo(validate=validate)
self._upConvertedKerningData = None
+ try:
+ self.readMetaInfo(validate=validate)
+ except UFOLibError:
+ self.close()
+ raise
+
# properties
def _get_path(self):
@@ -283,9 +290,26 @@ class UFOReader(_UFOBaseIO):
path = property(_get_path, doc="The path of the UFO (DEPRECATED).")
def _get_formatVersion(self):
- return self._formatVersion
+ import warnings
+
+ warnings.warn(
+ "The 'formatVersion' attribute is deprecated; use the 'formatVersionTuple'",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self._formatVersion.major
+
+ formatVersion = property(
+ _get_formatVersion,
+ doc="The (major) format version of the UFO. DEPRECATED: Use formatVersionTuple"
+ )
- formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is determined by reading metainfo.plist during __init__.")
+ @property
+ def formatVersionTuple(self):
+ """The (major, minor) format version of the UFO.
+ This is determined by reading metainfo.plist during __init__.
+ """
+ return self._formatVersion
def _get_fileStructure(self):
return self._fileStructure
@@ -324,12 +348,12 @@ class UFOReader(_UFOBaseIO):
if not isinstance(groups, dict):
raise UFOLibError(invalidFormatMessage)
for groupName, glyphList in groups.items():
- if not isinstance(groupName, basestring):
+ if not isinstance(groupName, str):
raise UFOLibError(invalidFormatMessage)
elif not isinstance(glyphList, list):
raise UFOLibError(invalidFormatMessage)
for glyphName in glyphList:
- if not isinstance(glyphName, basestring):
+ if not isinstance(glyphName, str):
raise UFOLibError(invalidFormatMessage)
self._upConvertedKerningData = dict(
kerning={},
@@ -340,7 +364,8 @@ class UFOReader(_UFOBaseIO):
# convert kerning and groups
kerning, groups, conversionMaps = convertUFO1OrUFO2KerningToUFO3Kerning(
self._upConvertedKerningData["originalKerning"],
- deepcopy(self._upConvertedKerningData["originalGroups"])
+ deepcopy(self._upConvertedKerningData["originalGroups"]),
+ self.getGlyphSet()
)
# store
self._upConvertedKerningData["kerning"] = kerning
@@ -366,7 +391,7 @@ class UFOReader(_UFOBaseIO):
The path must be relative to the UFO path.
Returns None if the file does not exist.
By default the file is opened in binary mode (reads bytes).
- If encoding is passed, the file is opened in text mode (reads unicode).
+ If encoding is passed, the file is opened in text mode (reads str).
Note: The caller is responsible for closing the open file.
"""
@@ -380,9 +405,9 @@ class UFOReader(_UFOBaseIO):
return None
# metainfo.plist
- def readMetaInfo(self, validate=None):
+ def _readMetaInfo(self, validate=None):
"""
- Read metainfo.plist. Only used for internal operations.
+ Read metainfo.plist and return raw data. Only used for internal operations.
``validate`` will validate the read data, by default it is set
to the class's validate value, can be overridden.
@@ -392,24 +417,54 @@ class UFOReader(_UFOBaseIO):
data = self._getPlist(METAINFO_FILENAME)
if validate and not isinstance(data, dict):
raise UFOLibError("metainfo.plist is not properly formatted.")
- formatVersion = data["formatVersion"]
- if validate:
- if not isinstance(formatVersion, int):
- raise UFOLibError(
- "formatVersion must be specified as an integer in '%s' on %s"
- % (METAINFO_FILENAME, self.fs)
- )
- if formatVersion not in supportedUFOFormatVersions:
- raise UFOLibError(
- "Unsupported UFO format (%d) in '%s' on %s"
- % (formatVersion, METAINFO_FILENAME, self.fs)
- )
- self._formatVersion = formatVersion
+ try:
+ formatVersionMajor = data["formatVersion"]
+ except KeyError:
+ raise UFOLibError(
+ f"Missing required formatVersion in '{METAINFO_FILENAME}' on {self.fs}"
+ )
+ formatVersionMinor = data.setdefault("formatVersionMinor", 0)
+
+ try:
+ formatVersion = UFOFormatVersion((formatVersionMajor, formatVersionMinor))
+ except ValueError as e:
+ unsupportedMsg = (
+ f"Unsupported UFO format ({formatVersionMajor}.{formatVersionMinor}) "
+ f"in '{METAINFO_FILENAME}' on {self.fs}"
+ )
+ if validate:
+ from fontTools.ufoLib.errors import UnsupportedUFOFormat
+
+ raise UnsupportedUFOFormat(unsupportedMsg) from e
+
+ formatVersion = UFOFormatVersion.default()
+ logger.warning(
+ "%s. Assuming the latest supported version (%s). "
+ "Some data may be skipped or parsed incorrectly",
+ unsupportedMsg, formatVersion
+ )
+ data["formatVersionTuple"] = formatVersion
+ return data
+
+ def readMetaInfo(self, validate=None):
+ """
+ Read metainfo.plist and set formatVersion. Only used for internal operations.
+
+ ``validate`` will validate the read data, by default it is set
+ to the class's validate value, can be overridden.
+ """
+ data = self._readMetaInfo(validate=validate)
+ self._formatVersion = data["formatVersionTuple"]
# groups.plist
def _readGroups(self):
- return self._getPlist(GROUPS_FILENAME, {})
+ groups = self._getPlist(GROUPS_FILENAME, {})
+ # remove any duplicate glyphs in a kerning group
+ for groupName, glyphList in groups.items():
+ if groupName.startswith(('public.kern1.', 'public.kern2.')):
+ groups[groupName] = list(OrderedDict.fromkeys(glyphList))
+ return groups
def readGroups(self, validate=None):
"""
@@ -420,7 +475,7 @@ class UFOReader(_UFOBaseIO):
if validate is None:
validate = self._validate
# handle up conversion
- if self._formatVersion < 3:
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
self._upConvertKerning(validate)
groups = self._upConvertedKerningData["groups"]
# normal
@@ -451,7 +506,7 @@ class UFOReader(_UFOBaseIO):
"""
if validate is None:
validate = self._validate
- if self._formatVersion >= 3:
+ if self._formatVersion >= UFOFormatVersion.FORMAT_3_0:
return dict(side1={}, side2={})
# use the public group reader to force the load and
# conversion of the data if it hasn't happened yet.
@@ -481,7 +536,7 @@ class UFOReader(_UFOBaseIO):
infoDict = self._readInfo(validate)
infoDataToSet = {}
# version 1
- if self._formatVersion == 1:
+ if self._formatVersion == UFOFormatVersion.FORMAT_1_0:
for attr in fontInfoAttributesVersion1:
value = infoDict.get(attr)
if value is not None:
@@ -489,15 +544,15 @@ class UFOReader(_UFOBaseIO):
infoDataToSet = _convertFontInfoDataVersion1ToVersion2(infoDataToSet)
infoDataToSet = _convertFontInfoDataVersion2ToVersion3(infoDataToSet)
# version 2
- elif self._formatVersion == 2:
+ elif self._formatVersion == UFOFormatVersion.FORMAT_2_0:
for attr, dataValidationDict in list(fontInfoAttributesVersion2ValueData.items()):
value = infoDict.get(attr)
if value is None:
continue
infoDataToSet[attr] = value
infoDataToSet = _convertFontInfoDataVersion2ToVersion3(infoDataToSet)
- # version 3
- elif self._formatVersion == 3:
+ # version 3.x
+ elif self._formatVersion.major == UFOFormatVersion.FORMAT_3_0.major:
for attr, dataValidationDict in list(fontInfoAttributesVersion3ValueData.items()):
value = infoDict.get(attr)
if value is None:
@@ -505,7 +560,7 @@ class UFOReader(_UFOBaseIO):
infoDataToSet[attr] = value
# unsupported version
else:
- raise NotImplementedError
+ raise NotImplementedError(self._formatVersion)
# validate data
if validate:
infoDataToSet = validateInfoVersion3Data(infoDataToSet)
@@ -532,7 +587,7 @@ class UFOReader(_UFOBaseIO):
if validate is None:
validate = self._validate
# handle up conversion
- if self._formatVersion < 3:
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
self._upConvertKerning(validate)
kerningNested = self._upConvertedKerningData["kerning"]
# normal
@@ -572,7 +627,7 @@ class UFOReader(_UFOBaseIO):
def readFeatures(self):
"""
- Read features.fea. Return a unicode string.
+ Read features.fea. Return a string.
The returned string is empty if the file is missing.
"""
try:
@@ -590,7 +645,7 @@ class UFOReader(_UFOBaseIO):
``validate`` will validate the layer contents.
"""
- if self._formatVersion < 3:
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
return [(DEFAULT_LAYER_NAME, DEFAULT_GLYPHS_DIRNAME)]
contents = self._getPlist(LAYERCONTENTS_FILENAME)
if validate:
@@ -638,7 +693,7 @@ class UFOReader(_UFOBaseIO):
``validateRead`` will validate the read data, by default it is set to the
class's validate value, can be overridden.
- ``validateWrte`` will validate the written data, by default it is set to the
+ ``validateWrite`` will validate the written data, by default it is set to the
class's validate value, can be overridden.
"""
from fontTools.ufoLib.glifLib import GlyphSet
@@ -661,13 +716,14 @@ class UFOReader(_UFOBaseIO):
glyphSubFS = self.fs.opendir(directory)
except fs.errors.ResourceNotFound:
raise UFOLibError(
- "No '%s' directory for layer '%s'" % (directory, layerName)
+ f"No '{directory}' directory for layer '{layerName}'"
)
return GlyphSet(
glyphSubFS,
ufoFormatVersion=self._formatVersion,
validateRead=validateRead,
validateWrite=validateWrite,
+ expectContentsFile=True
)
def getCharacterMapping(self, layerName=None, validate=None):
@@ -722,7 +778,7 @@ class UFOReader(_UFOBaseIO):
``validate`` will validate the data, by default it is set to the
class's validate value, can be overridden.
"""
- if self._formatVersion < 3:
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
return []
if validate is None:
validate = self._validate
@@ -760,7 +816,7 @@ class UFOReader(_UFOBaseIO):
dataFS = self.fs.opendir(DATA_DIRNAME)
data = dataFS.readbytes(fileName)
except fs.errors.ResourceNotFound:
- raise UFOLibError("No data file named '%s' on %s" % (fileName, self.fs))
+ raise UFOLibError(f"No data file named '{fileName}' on {self.fs}")
return data
def readImage(self, fileName, validate=None):
@@ -772,8 +828,10 @@ class UFOReader(_UFOBaseIO):
"""
if validate is None:
validate = self._validate
- if self._formatVersion < 3:
- raise UFOLibError("Reading images is not allowed in UFO %d." % self._formatVersion)
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
+ raise UFOLibError(
+ f"Reading images is not allowed in UFO {self._formatVersion.major}."
+ )
fileName = fsdecode(fileName)
try:
try:
@@ -783,7 +841,7 @@ class UFOReader(_UFOBaseIO):
imagesFS = self.fs.opendir(IMAGES_DIRNAME)
data = imagesFS.readbytes(fileName)
except fs.errors.ResourceNotFound:
- raise UFOLibError("No image file named '%s' on %s" % (fileName, self.fs))
+ raise UFOLibError(f"No image file named '{fileName}' on {self.fs}")
if validate:
valid, error = pngValidator(data=data)
if not valid:
@@ -813,23 +871,35 @@ class UFOWriter(UFOReader):
By default, the written data will be validated before writing. Set ``validate`` to
``False`` if you do not want to validate the data. Validation can also be overriden
on a per method level if desired.
+
+ The ``formatVersion`` argument allows to specify the UFO format version as a tuple
+ of integers (major, minor), or as a single integer for the major digit only (minor
+ is implied as 0). By default the latest formatVersion will be used; currently it's
+ 3.0, which is equivalent to formatVersion=(3, 0).
+
+ An UnsupportedUFOFormat exception is raised if the requested UFO formatVersion is
+ not supported.
"""
def __init__(
self,
path,
- formatVersion=3,
+ formatVersion=None,
fileCreator="com.github.fonttools.ufoLib",
structure=None,
validate=True,
):
- if formatVersion not in supportedUFOFormatVersions:
- raise UFOLibError("Unsupported UFO format (%d)." % formatVersion)
+ try:
+ formatVersion = UFOFormatVersion(formatVersion)
+ except ValueError as e:
+ from fontTools.ufoLib.errors import UnsupportedUFOFormat
+
+ raise UnsupportedUFOFormat(f"Unsupported UFO format: {formatVersion!r}") from e
if hasattr(path, "__fspath__"): # support os.PathLike objects
path = path.__fspath__()
- if isinstance(path, basestring):
+ if isinstance(path, str):
# normalize path by removing trailing or double slashes
path = os.path.normpath(path)
havePreviousFile = os.path.exists(path)
@@ -909,7 +979,7 @@ class UFOWriter(UFOReader):
path = filesystem.getsyspath("/")
except fs.errors.NoSysPath:
# network or in-memory FS may not map to the local one
- path = unicode(filesystem)
+ path = str(filesystem)
# if passed an FS object, always use 'package' structure
if structure and structure is not UFOFileStructure.PACKAGE:
import warnings
@@ -940,22 +1010,20 @@ class UFOWriter(UFOReader):
# this will be needed for up and down conversion.
previousFormatVersion = None
if self._havePreviousFile:
- metaInfo = self._getPlist(METAINFO_FILENAME)
- previousFormatVersion = metaInfo.get("formatVersion")
- try:
- previousFormatVersion = int(previousFormatVersion)
- except (ValueError, TypeError):
- self.fs.close()
- raise UFOLibError("The existing metainfo.plist is not properly formatted.")
- if previousFormatVersion not in supportedUFOFormatVersions:
- self.fs.close()
- raise UFOLibError("Unsupported UFO format (%d)." % formatVersion)
- # catch down conversion
- if previousFormatVersion is not None and previousFormatVersion > formatVersion:
- raise UFOLibError("The UFO located at this path is a higher version (%d) than the version (%d) that is trying to be written. This is not supported." % (previousFormatVersion, formatVersion))
+ metaInfo = self._readMetaInfo(validate=validate)
+ previousFormatVersion = metaInfo["formatVersionTuple"]
+ # catch down conversion
+ if previousFormatVersion > formatVersion:
+ from fontTools.ufoLib.errors import UnsupportedUFOFormat
+
+ raise UnsupportedUFOFormat(
+ "The UFO located at this path is a higher version "
+ f"({previousFormatVersion}) than the version ({formatVersion}) "
+ "that is trying to be written. This is not supported."
+ )
# handle the layer contents
self.layerContents = {}
- if previousFormatVersion is not None and previousFormatVersion >= 3:
+ if previousFormatVersion is not None and previousFormatVersion.major >= 3:
# already exists
self.layerContents = OrderedDict(self._readLayerContents(validate))
else:
@@ -1040,7 +1108,7 @@ class UFOWriter(UFOReader):
return self.fs.open(path, mode=mode, encoding=encoding)
except fs.errors.ResourceError as e:
return UFOLibError(
- "unable to open '%s' on %s: %s" % (path, self.fs, e)
+ f"unable to open '{path}' on {self.fs}: {e}"
)
def removePath(self, path, force=False, removeEmptyParents=True):
@@ -1060,7 +1128,7 @@ class UFOWriter(UFOReader):
except fs.errors.ResourceNotFound:
if not force:
raise UFOLibError(
- "'%s' does not exist on %s" % (path, self.fs)
+ f"'{path}' does not exist on {self.fs}"
)
if removeEmptyParents:
parent = fs.path.dirname(path)
@@ -1091,8 +1159,10 @@ class UFOWriter(UFOReader):
def _writeMetaInfo(self):
metaInfo = dict(
creator=self._fileCreator,
- formatVersion=self._formatVersion
+ formatVersion=self._formatVersion.major,
)
+ if self._formatVersion.minor != 0:
+ metaInfo["formatVersionMinor"] = self._formatVersion.minor
self._writePlist(METAINFO_FILENAME, metaInfo)
# groups.plist
@@ -1113,7 +1183,7 @@ class UFOWriter(UFOReader):
This is the same form returned by UFOReader's
getKerningGroupConversionRenameMaps method.
"""
- if self._formatVersion >= 3:
+ if self._formatVersion >= UFOFormatVersion.FORMAT_3_0:
return # XXX raise an error here
# flip the dictionaries
remap = {}
@@ -1138,7 +1208,10 @@ class UFOWriter(UFOReader):
if not valid:
raise UFOLibError(message)
# down convert
- if self._formatVersion < 3 and self._downConversionKerningData is not None:
+ if (
+ self._formatVersion < UFOFormatVersion.FORMAT_3_0
+ and self._downConversionKerningData is not None
+ ):
remap = self._downConversionKerningData["groupRenameMap"]
remappedGroups = {}
# there are some edge cases here that are ignored:
@@ -1199,20 +1272,21 @@ class UFOWriter(UFOReader):
continue
infoData[attr] = value
# down convert data if necessary and validate
- if self._formatVersion == 3:
+ if self._formatVersion == UFOFormatVersion.FORMAT_3_0:
if validate:
infoData = validateInfoVersion3Data(infoData)
- elif self._formatVersion == 2:
+ elif self._formatVersion == UFOFormatVersion.FORMAT_2_0:
infoData = _convertFontInfoDataVersion3ToVersion2(infoData)
if validate:
infoData = validateInfoVersion2Data(infoData)
- elif self._formatVersion == 1:
+ elif self._formatVersion == UFOFormatVersion.FORMAT_1_0:
infoData = _convertFontInfoDataVersion3ToVersion2(infoData)
if validate:
infoData = validateInfoVersion2Data(infoData)
infoData = _convertFontInfoDataVersion2ToVersion1(infoData)
- # write file
- self._writePlist(FONTINFO_FILENAME, infoData)
+ # write file if there is anything to write
+ if infoData:
+ self._writePlist(FONTINFO_FILENAME, infoData)
# kerning.plist
@@ -1241,14 +1315,17 @@ class UFOWriter(UFOReader):
raise UFOLibError(invalidFormatMessage)
if not len(pair) == 2:
raise UFOLibError(invalidFormatMessage)
- if not isinstance(pair[0], basestring):
+ if not isinstance(pair[0], str):
raise UFOLibError(invalidFormatMessage)
- if not isinstance(pair[1], basestring):
+ if not isinstance(pair[1], str):
raise UFOLibError(invalidFormatMessage)
if not isinstance(value, numberTypes):
raise UFOLibError(invalidFormatMessage)
# down convert
- if self._formatVersion < 3 and self._downConversionKerningData is not None:
+ if (
+ self._formatVersion < UFOFormatVersion.FORMAT_3_0
+ and self._downConversionKerningData is not None
+ ):
remap = self._downConversionKerningData["groupRenameMap"]
remappedKerning = {}
for (side1, side2), value in list(kerning.items()):
@@ -1298,10 +1375,10 @@ class UFOWriter(UFOReader):
"""
if validate is None:
validate = self._validate
- if self._formatVersion == 1:
+ if self._formatVersion == UFOFormatVersion.FORMAT_1_0:
raise UFOLibError("features.fea is not allowed in UFO Format Version 1.")
if validate:
- if not isinstance(features, basestring):
+ if not isinstance(features, str):
raise UFOLibError("The features are not text.")
if features:
self.writeBytesToPath(FEATURES_FILENAME, features.encode("utf8"))
@@ -1317,15 +1394,13 @@ class UFOWriter(UFOReader):
"""
if validate is None:
validate = self._validate
- if self.formatVersion < 3:
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
return
if layerOrder is not None:
newOrder = []
for layerName in layerOrder:
if layerName is None:
layerName = DEFAULT_LAYER_NAME
- else:
- layerName = tounicode(layerName)
newOrder.append(layerName)
layerOrder = newOrder
else:
@@ -1348,7 +1423,15 @@ class UFOWriter(UFOReader):
raise UFOLibError("Could not locate a glyph set directory for the layer named %s." % layerName)
return foundDirectory
- def getGlyphSet(self, layerName=None, defaultLayer=True, glyphNameToFileNameFunc=None, validateRead=None, validateWrite=None):
+ def getGlyphSet(
+ self,
+ layerName=None,
+ defaultLayer=True,
+ glyphNameToFileNameFunc=None,
+ validateRead=None,
+ validateWrite=None,
+ expectContentsFile=False,
+ ):
"""
Return the GlyphSet object associated with the
appropriate glyph directory in the .ufo.
@@ -1361,14 +1444,23 @@ class UFOWriter(UFOReader):
class's validate value, can be overridden.
``validateWrte`` will validate the written data, by default it is set to the
class's validate value, can be overridden.
+ ``expectContentsFile`` will raise a GlifLibError if a contents.plist file is
+ not found on the glyph set file system. This should be set to ``True`` if you
+ are reading an existing UFO and ``False`` if you use ``getGlyphSet`` to create
+ a fresh glyph set.
"""
if validateRead is None:
validateRead = self._validate
if validateWrite is None:
validateWrite = self._validate
# only default can be written in < 3
- if self._formatVersion < 3 and (not defaultLayer or layerName is not None):
- raise UFOLibError("Only the default layer can be writen in UFO %d." % self.formatVersion)
+ if (
+ self._formatVersion < UFOFormatVersion.FORMAT_3_0
+ and (not defaultLayer or layerName is not None)
+ ):
+ raise UFOLibError(
+ f"Only the default layer can be writen in UFO {self._formatVersion.major}."
+ )
# locate a layer name when None has been given
if layerName is None and defaultLayer:
for existingLayerName, directory in self.layerContents.items():
@@ -1379,40 +1471,53 @@ class UFOWriter(UFOReader):
elif layerName is None and not defaultLayer:
raise UFOLibError("A layer name must be provided for non-default layers.")
# move along to format specific writing
- if self.formatVersion == 1:
- return self._getGlyphSetFormatVersion1(validateRead, validateWrite, glyphNameToFileNameFunc=glyphNameToFileNameFunc)
- elif self.formatVersion == 2:
- return self._getGlyphSetFormatVersion2(validateRead, validateWrite, glyphNameToFileNameFunc=glyphNameToFileNameFunc)
- elif self.formatVersion == 3:
- return self._getGlyphSetFormatVersion3(validateRead, validateWrite, layerName=layerName, defaultLayer=defaultLayer, glyphNameToFileNameFunc=glyphNameToFileNameFunc)
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
+ return self._getDefaultGlyphSet(
+ validateRead,
+ validateWrite,
+ glyphNameToFileNameFunc=glyphNameToFileNameFunc,
+ expectContentsFile=expectContentsFile
+ )
+ elif self._formatVersion.major == UFOFormatVersion.FORMAT_3_0.major:
+ return self._getGlyphSetFormatVersion3(
+ validateRead,
+ validateWrite,
+ layerName=layerName,
+ defaultLayer=defaultLayer,
+ glyphNameToFileNameFunc=glyphNameToFileNameFunc,
+ expectContentsFile=expectContentsFile,
+ )
else:
- raise AssertionError(self.formatVersion)
-
- def _getGlyphSetFormatVersion1(self, validateRead, validateWrite, glyphNameToFileNameFunc=None):
- from fontTools.ufoLib.glifLib import GlyphSet
-
- glyphSubFS = self.fs.makedir(DEFAULT_GLYPHS_DIRNAME, recreate=True)
- return GlyphSet(
- glyphSubFS,
- glyphNameToFileNameFunc=glyphNameToFileNameFunc,
- ufoFormatVersion=1,
- validateRead=validateRead,
- validateWrite=validateWrite,
- )
+ raise NotImplementedError(self._formatVersion)
- def _getGlyphSetFormatVersion2(self, validateRead, validateWrite, glyphNameToFileNameFunc=None):
+ def _getDefaultGlyphSet(
+ self,
+ validateRead,
+ validateWrite,
+ glyphNameToFileNameFunc=None,
+ expectContentsFile=False,
+ ):
from fontTools.ufoLib.glifLib import GlyphSet
glyphSubFS = self.fs.makedir(DEFAULT_GLYPHS_DIRNAME, recreate=True)
return GlyphSet(
glyphSubFS,
glyphNameToFileNameFunc=glyphNameToFileNameFunc,
- ufoFormatVersion=2,
+ ufoFormatVersion=self._formatVersion,
validateRead=validateRead,
validateWrite=validateWrite,
+ expectContentsFile=expectContentsFile,
)
- def _getGlyphSetFormatVersion3(self, validateRead, validateWrite, layerName=None, defaultLayer=True, glyphNameToFileNameFunc=None):
+ def _getGlyphSetFormatVersion3(
+ self,
+ validateRead,
+ validateWrite,
+ layerName=None,
+ defaultLayer=True,
+ glyphNameToFileNameFunc=None,
+ expectContentsFile=False,
+ ):
from fontTools.ufoLib.glifLib import GlyphSet
# if the default flag is on, make sure that the default in the file
@@ -1439,11 +1544,6 @@ class UFOWriter(UFOReader):
# not caching this could be slightly expensive,
# but caching it will be cumbersome
existing = {d.lower() for d in self.layerContents.values()}
- if not isinstance(layerName, unicode):
- try:
- layerName = unicode(layerName)
- except UnicodeDecodeError:
- raise UFOLibError("The specified layer name is not a Unicode string.")
directory = userNameToFileName(layerName, existing=existing, prefix="glyphs.")
# make the directory
glyphSubFS = self.fs.makedir(directory, recreate=True)
@@ -1453,9 +1553,10 @@ class UFOWriter(UFOReader):
return GlyphSet(
glyphSubFS,
glyphNameToFileNameFunc=glyphNameToFileNameFunc,
- ufoFormatVersion=3,
+ ufoFormatVersion=self._formatVersion,
validateRead=validateRead,
validateWrite=validateWrite,
+ expectContentsFile=expectContentsFile,
)
def renameGlyphSet(self, layerName, newLayerName, defaultLayer=False):
@@ -1466,7 +1567,7 @@ class UFOWriter(UFOReader):
layerName, it is up to the caller to inform that object that
the directory it represents has changed.
"""
- if self._formatVersion < 3:
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
# ignore renaming glyph sets for UFO1 UFO2
# just write the data from the default layer
return
@@ -1505,7 +1606,7 @@ class UFOWriter(UFOReader):
"""
Remove the glyph set matching layerName.
"""
- if self._formatVersion < 3:
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
# ignore deleting glyph sets for UFO1 UFO2 as there are no layers
# just write the data from the default layer
return
@@ -1518,13 +1619,13 @@ class UFOWriter(UFOReader):
Write data to fileName in the 'data' directory.
The data must be a bytes string.
"""
- self.writeBytesToPath("%s/%s" % (DATA_DIRNAME, fsdecode(fileName)), data)
+ self.writeBytesToPath(f"{DATA_DIRNAME}/{fsdecode(fileName)}", data)
def removeData(self, fileName):
"""
Remove the file named fileName from the data directory.
"""
- self.removePath("%s/%s" % (DATA_DIRNAME, fsdecode(fileName)))
+ self.removePath(f"{DATA_DIRNAME}/{fsdecode(fileName)}")
# /images
@@ -1535,23 +1636,27 @@ class UFOWriter(UFOReader):
"""
if validate is None:
validate = self._validate
- if self._formatVersion < 3:
- raise UFOLibError("Images are not allowed in UFO %d." % self._formatVersion)
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
+ raise UFOLibError(
+ f"Images are not allowed in UFO {self._formatVersion.major}."
+ )
fileName = fsdecode(fileName)
if validate:
valid, error = pngValidator(data=data)
if not valid:
raise UFOLibError(error)
- self.writeBytesToPath("%s/%s" % (IMAGES_DIRNAME, fileName), data)
+ self.writeBytesToPath(f"{IMAGES_DIRNAME}/{fileName}", data)
def removeImage(self, fileName, validate=None): # XXX remove unused 'validate'?
"""
Remove the file named fileName from the
images directory.
"""
- if self._formatVersion < 3:
- raise UFOLibError("Images are not allowed in UFO %d." % self._formatVersion)
- self.removePath("%s/%s" % (IMAGES_DIRNAME, fsdecode(fileName)))
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
+ raise UFOLibError(
+ f"Images are not allowed in UFO {self._formatVersion.major}."
+ )
+ self.removePath(f"{IMAGES_DIRNAME}/{fsdecode(fileName)}")
def copyImageFromReader(self, reader, sourceFileName, destFileName, validate=None):
"""
@@ -1561,10 +1666,12 @@ class UFOWriter(UFOReader):
"""
if validate is None:
validate = self._validate
- if self._formatVersion < 3:
- raise UFOLibError("Images are not allowed in UFO %d." % self._formatVersion)
- sourcePath = "%s/%s" % (IMAGES_DIRNAME, fsdecode(sourceFileName))
- destPath = "%s/%s" % (IMAGES_DIRNAME, fsdecode(destFileName))
+ if self._formatVersion < UFOFormatVersion.FORMAT_3_0:
+ raise UFOLibError(
+ f"Images are not allowed in UFO {self._formatVersion.major}."
+ )
+ sourcePath = f"{IMAGES_DIRNAME}/{fsdecode(sourceFileName)}"
+ destPath = f"{IMAGES_DIRNAME}/{fsdecode(destFileName)}"
self.copyFromReader(reader, sourcePath, destPath)
def close(self):
@@ -1574,7 +1681,7 @@ class UFOWriter(UFOReader):
rootDir = os.path.splitext(os.path.basename(self._path))[0] + ".ufo"
with fs.zipfs.ZipFS(self._path, write=True, encoding="utf-8") as destFS:
fs.copy.copy_fs(self.fs, destFS.makedir(rootDir))
- super(UFOWriter, self).close()
+ super().close()
# just an alias, makes it more explicit
@@ -1587,7 +1694,7 @@ UFOReaderWriter = UFOWriter
def _sniffFileStructure(ufo_path):
- """Return UFOFileStructure.ZIP if the UFO at path 'ufo_path' (basestring)
+ """Return UFOFileStructure.ZIP if the UFO at path 'ufo_path' (str)
is a zip file, else return UFOFileStructure.PACKAGE if 'ufo_path' is a
directory.
Raise UFOLibError if it is a file with unknown structure, or if the path
@@ -1669,7 +1776,7 @@ def validateInfoVersion2Data(infoData):
for attr, value in list(infoData.items()):
isValidValue = validateFontInfoVersion2ValueForAttribute(attr, value)
if not isValidValue:
- raise UFOLibError("Invalid value for attribute %s (%s)." % (attr, repr(value)))
+ raise UFOLibError(f"Invalid value for attribute {attr} ({value!r}).")
else:
validInfoData[attr] = value
return validInfoData
@@ -1713,7 +1820,7 @@ def validateInfoVersion3Data(infoData):
for attr, value in list(infoData.items()):
isValidValue = validateFontInfoVersion3ValueForAttribute(attr, value)
if not isValidValue:
- raise UFOLibError("Invalid value for attribute %s (%s)." % (attr, repr(value)))
+ raise UFOLibError(f"Invalid value for attribute {attr} ({value!r}).")
else:
validInfoData[attr] = value
return validInfoData
@@ -1731,7 +1838,7 @@ fontInfoOpenTypeOS2TypeOptions = [0, 1, 2, 3, 8, 9]
# cases the possible values, that can exist is
# fontinfo.plist.
-fontInfoAttributesVersion1 = set([
+fontInfoAttributesVersion1 = {
"familyName",
"styleName",
"fullName",
@@ -1772,26 +1879,26 @@ fontInfoAttributesVersion1 = set([
"ttVendor",
"ttUniqueID",
"ttVersion",
-])
+}
fontInfoAttributesVersion2ValueData = {
- "familyName" : dict(type=basestring),
- "styleName" : dict(type=basestring),
- "styleMapFamilyName" : dict(type=basestring),
- "styleMapStyleName" : dict(type=basestring, valueValidator=fontInfoStyleMapStyleNameValidator),
+ "familyName" : dict(type=str),
+ "styleName" : dict(type=str),
+ "styleMapFamilyName" : dict(type=str),
+ "styleMapStyleName" : dict(type=str, valueValidator=fontInfoStyleMapStyleNameValidator),
"versionMajor" : dict(type=int),
"versionMinor" : dict(type=int),
"year" : dict(type=int),
- "copyright" : dict(type=basestring),
- "trademark" : dict(type=basestring),
+ "copyright" : dict(type=str),
+ "trademark" : dict(type=str),
"unitsPerEm" : dict(type=(int, float)),
"descender" : dict(type=(int, float)),
"xHeight" : dict(type=(int, float)),
"capHeight" : dict(type=(int, float)),
"ascender" : dict(type=(int, float)),
"italicAngle" : dict(type=(float, int)),
- "note" : dict(type=basestring),
- "openTypeHeadCreated" : dict(type=basestring, valueValidator=fontInfoOpenTypeHeadCreatedValidator),
+ "note" : dict(type=str),
+ "openTypeHeadCreated" : dict(type=str, valueValidator=fontInfoOpenTypeHeadCreatedValidator),
"openTypeHeadLowestRecPPEM" : dict(type=(int, float)),
"openTypeHeadFlags" : dict(type="integerList", valueValidator=genericIntListValidator, valueOptions=fontInfoOpenTypeHeadFlagsOptions),
"openTypeHheaAscender" : dict(type=(int, float)),
@@ -1800,25 +1907,25 @@ fontInfoAttributesVersion2ValueData = {
"openTypeHheaCaretSlopeRise" : dict(type=int),
"openTypeHheaCaretSlopeRun" : dict(type=int),
"openTypeHheaCaretOffset" : dict(type=(int, float)),
- "openTypeNameDesigner" : dict(type=basestring),
- "openTypeNameDesignerURL" : dict(type=basestring),
- "openTypeNameManufacturer" : dict(type=basestring),
- "openTypeNameManufacturerURL" : dict(type=basestring),
- "openTypeNameLicense" : dict(type=basestring),
- "openTypeNameLicenseURL" : dict(type=basestring),
- "openTypeNameVersion" : dict(type=basestring),
- "openTypeNameUniqueID" : dict(type=basestring),
- "openTypeNameDescription" : dict(type=basestring),
- "openTypeNamePreferredFamilyName" : dict(type=basestring),
- "openTypeNamePreferredSubfamilyName" : dict(type=basestring),
- "openTypeNameCompatibleFullName" : dict(type=basestring),
- "openTypeNameSampleText" : dict(type=basestring),
- "openTypeNameWWSFamilyName" : dict(type=basestring),
- "openTypeNameWWSSubfamilyName" : dict(type=basestring),
+ "openTypeNameDesigner" : dict(type=str),
+ "openTypeNameDesignerURL" : dict(type=str),
+ "openTypeNameManufacturer" : dict(type=str),
+ "openTypeNameManufacturerURL" : dict(type=str),
+ "openTypeNameLicense" : dict(type=str),
+ "openTypeNameLicenseURL" : dict(type=str),
+ "openTypeNameVersion" : dict(type=str),
+ "openTypeNameUniqueID" : dict(type=str),
+ "openTypeNameDescription" : dict(type=str),
+ "openTypeNamePreferredFamilyName" : dict(type=str),
+ "openTypeNamePreferredSubfamilyName" : dict(type=str),
+ "openTypeNameCompatibleFullName" : dict(type=str),
+ "openTypeNameSampleText" : dict(type=str),
+ "openTypeNameWWSFamilyName" : dict(type=str),
+ "openTypeNameWWSSubfamilyName" : dict(type=str),
"openTypeOS2WidthClass" : dict(type=int, valueValidator=fontInfoOpenTypeOS2WidthClassValidator),
"openTypeOS2WeightClass" : dict(type=int, valueValidator=fontInfoOpenTypeOS2WeightClassValidator),
"openTypeOS2Selection" : dict(type="integerList", valueValidator=genericIntListValidator, valueOptions=fontInfoOpenTypeOS2SelectionOptions),
- "openTypeOS2VendorID" : dict(type=basestring),
+ "openTypeOS2VendorID" : dict(type=str),
"openTypeOS2Panose" : dict(type="integerList", valueValidator=fontInfoVersion2OpenTypeOS2PanoseValidator),
"openTypeOS2FamilyClass" : dict(type="integerList", valueValidator=fontInfoOpenTypeOS2FamilyClassValidator),
"openTypeOS2UnicodeRanges" : dict(type="integerList", valueValidator=genericIntListValidator, valueOptions=fontInfoOpenTypeOS2UnicodeRangesOptions),
@@ -1845,8 +1952,8 @@ fontInfoAttributesVersion2ValueData = {
"openTypeVheaCaretSlopeRise" : dict(type=int),
"openTypeVheaCaretSlopeRun" : dict(type=int),
"openTypeVheaCaretOffset" : dict(type=(int, float)),
- "postscriptFontName" : dict(type=basestring),
- "postscriptFullName" : dict(type=basestring),
+ "postscriptFontName" : dict(type=str),
+ "postscriptFullName" : dict(type=str),
"postscriptSlantAngle" : dict(type=(float, int)),
"postscriptUniqueID" : dict(type=int),
"postscriptUnderlineThickness" : dict(type=(int, float)),
@@ -1864,11 +1971,11 @@ fontInfoAttributesVersion2ValueData = {
"postscriptForceBold" : dict(type=bool),
"postscriptDefaultWidthX" : dict(type=(int, float)),
"postscriptNominalWidthX" : dict(type=(int, float)),
- "postscriptWeightName" : dict(type=basestring),
- "postscriptDefaultCharacter" : dict(type=basestring),
+ "postscriptWeightName" : dict(type=str),
+ "postscriptDefaultCharacter" : dict(type=str),
"postscriptWindowsCharacterSet" : dict(type=int, valueValidator=fontInfoPostscriptWindowsCharacterSetValidator),
"macintoshFONDFamilyID" : dict(type=int),
- "macintoshFONDName" : dict(type=basestring),
+ "macintoshFONDName" : dict(type=str),
}
fontInfoAttributesVersion2 = set(fontInfoAttributesVersion2ValueData.keys())
@@ -2043,17 +2150,17 @@ def convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value):
if attr == "fontStyle":
v = _fontStyle1To2.get(value)
if v is None:
- raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr))
+ raise UFOLibError(f"Cannot convert value ({value!r}) for attribute {attr}.")
value = v
elif attr == "widthName":
v = _widthName1To2.get(value)
if v is None:
- raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr))
+ raise UFOLibError(f"Cannot convert value ({value!r}) for attribute {attr}.")
value = v
elif attr == "msCharSet":
v = _msCharSet1To2.get(value)
if v is None:
- raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr))
+ raise UFOLibError(f"Cannot convert value ({value!r}) for attribute {attr}.")
value = v
attr = fontInfoAttributesVersion1To2.get(attr, attr)
return attr, value
@@ -2088,7 +2195,7 @@ def _convertFontInfoDataVersion1ToVersion2(data):
continue
# catch values that can't be converted
if value is None:
- raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr))
+ raise UFOLibError(f"Cannot convert value ({value!r}) for attribute {newAttr}.")
# store
converted[newAttr] = newValue
return converted
@@ -2102,23 +2209,23 @@ def _convertFontInfoDataVersion2ToVersion1(data):
continue
# catch values that can't be converted
if value is None:
- raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr))
+ raise UFOLibError(f"Cannot convert value ({value!r}) for attribute {newAttr}.")
# store
converted[newAttr] = newValue
return converted
# 2 <-> 3
-_ufo2To3NonNegativeInt = set((
+_ufo2To3NonNegativeInt = {
"versionMinor",
"openTypeHeadLowestRecPPEM",
"openTypeOS2WinAscent",
"openTypeOS2WinDescent"
-))
-_ufo2To3NonNegativeIntOrFloat = set((
- "unitsPerEm"
-))
-_ufo2To3FloatToInt = set(((
+}
+_ufo2To3NonNegativeIntOrFloat = {
+ "unitsPerEm",
+}
+_ufo2To3FloatToInt = {
"openTypeHeadLowestRecPPEM",
"openTypeHheaAscender",
"openTypeHheaDescender",
@@ -2143,7 +2250,7 @@ _ufo2To3FloatToInt = set(((
"openTypeVheaVertTypoDescender",
"openTypeVheaVertTypoLineGap",
"openTypeVheaCaretOffset"
-)))
+}
def convertFontInfoValueForAttributeFromVersion2ToVersion3(attr, value):
"""
@@ -2153,18 +2260,14 @@ def convertFontInfoValueForAttributeFromVersion2ToVersion3(attr, value):
"""
if attr in _ufo2To3FloatToInt:
try:
- v = int(round(value))
+ value = round(value)
except (ValueError, TypeError):
raise UFOLibError("Could not convert value for %s." % attr)
- if v != value:
- value = v
if attr in _ufo2To3NonNegativeInt:
try:
- v = int(abs(value))
+ value = int(abs(value))
except (ValueError, TypeError):
raise UFOLibError("Could not convert value for %s." % attr)
- if v != value:
- value = v
elif attr in _ufo2To3NonNegativeIntOrFloat:
try:
v = float(abs(value))
diff --git a/Lib/fontTools/ufoLib/converters.py b/Lib/fontTools/ufoLib/converters.py
index c9ec9906..3b8112c3 100644
--- a/Lib/fontTools/ufoLib/converters.py
+++ b/Lib/fontTools/ufoLib/converters.py
@@ -2,21 +2,20 @@
Conversion functions.
"""
-from __future__ import absolute_import, unicode_literals
# adapted from the UFO spec
-def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups):
+def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
# gather known kerning groups based on the prefixes
firstReferencedGroups, secondReferencedGroups = findKnownKerningGroups(groups)
# Make lists of groups referenced in kerning pairs.
for first, seconds in list(kerning.items()):
- if first in groups:
+ if first in groups and first not in glyphSet:
if not first.startswith("public.kern1."):
firstReferencedGroups.add(first)
for second in list(seconds.keys()):
- if second in groups:
+ if second in groups and second not in glyphSet:
if not second.startswith("public.kern2."):
secondReferencedGroups.add(second)
# Create new names for these groups.
@@ -155,7 +154,7 @@ def test():
... "DGroup" : ["D"],
... }
>>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning(
- ... testKerning, testGroups)
+ ... testKerning, testGroups, [])
>>> expected = {
... "A" : {
... "A": 1,
@@ -221,7 +220,7 @@ def test():
... "@MMK_R_XGroup" : ["X"],
... }
>>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning(
- ... testKerning, testGroups)
+ ... testKerning, testGroups, [])
>>> expected = {
... "A" : {
... "A": 1,
@@ -294,7 +293,7 @@ def test():
... "DGroup" : ["D"],
... }
>>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning(
- ... testKerning, testGroups)
+ ... testKerning, testGroups, [])
>>> expected = {
... "A" : {
... "A": 1,
diff --git a/Lib/fontTools/ufoLib/errors.py b/Lib/fontTools/ufoLib/errors.py
index fb048d1c..304345e4 100644
--- a/Lib/fontTools/ufoLib/errors.py
+++ b/Lib/fontTools/ufoLib/errors.py
@@ -1,9 +1,16 @@
-from __future__ import absolute_import, unicode_literals
class UFOLibError(Exception):
pass
+class UnsupportedUFOFormat(UFOLibError):
+ pass
+
+
class GlifLibError(UFOLibError):
pass
+
+
+class UnsupportedGLIFFormat(GlifLibError):
+ pass
diff --git a/Lib/fontTools/ufoLib/filenames.py b/Lib/fontTools/ufoLib/filenames.py
index 1c5630f0..2815469f 100644
--- a/Lib/fontTools/ufoLib/filenames.py
+++ b/Lib/fontTools/ufoLib/filenames.py
@@ -1,10 +1,7 @@
"""
User name to file name conversion.
-This was taken form the UFO 3 spec.
+This was taken from the UFO 3 spec.
"""
-from __future__ import absolute_import, unicode_literals
-from fontTools.misc.py23 import basestring, unicode
-
illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
illegalCharacters += [chr(i) for i in range(1, 32)]
@@ -18,7 +15,7 @@ class NameTranslationError(Exception):
pass
-def userNameToFileName(userName, existing=[], prefix="", suffix=""):
+def userNameToFileName(userName: str, existing=[], prefix="", suffix=""):
"""
existing should be a case-insensitive list
of all existing file names.
@@ -68,9 +65,9 @@ def userNameToFileName(userName, existing=[], prefix="", suffix=""):
>>> userNameToFileName("alt.con") == "alt._con"
True
"""
- # the incoming name must be a unicode string
- if not isinstance(userName, unicode):
- raise ValueError("The value for userName must be a unicode string.")
+ # the incoming name must be a string
+ if not isinstance(userName, str):
+ raise ValueError("The value for userName must be a string.")
# establish the prefix and suffix lengths
prefixLength = len(prefix)
suffixLength = len(suffix)
diff --git a/Lib/fontTools/ufoLib/glifLib.py b/Lib/fontTools/ufoLib/glifLib.py
index 99dd2cfa..3003110e 100755
--- a/Lib/fontTools/ufoLib/glifLib.py
+++ b/Lib/fontTools/ufoLib/glifLib.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
glifLib.py -- Generic module for reading and writing the .glif format.
@@ -11,7 +10,8 @@ in a folder. It offers two ways to read glyph data, and one way to write
glyph data. See the class doc string for details.
"""
-from __future__ import absolute_import, unicode_literals
+import logging
+import enum
from warnings import warn
from collections import OrderedDict
import fs
@@ -19,7 +19,7 @@ import fs.base
import fs.errors
import fs.osfs
import fs.path
-from fontTools.misc.py23 import basestring, unicode, tobytes, tounicode
+from fontTools.misc.py23 import tobytes
from fontTools.misc import plistlib
from fontTools.pens.pointPen import AbstractPointPen, PointToSegmentPen
from fontTools.ufoLib.errors import GlifLibError
@@ -34,8 +34,8 @@ from fontTools.ufoLib.validators import (
glyphLibValidator,
)
from fontTools.misc import etree
-from fontTools.ufoLib import _UFOBaseIO
-from fontTools.ufoLib.utils import integerTypes, numberTypes
+from fontTools.ufoLib import _UFOBaseIO, UFOFormatVersion
+from fontTools.ufoLib.utils import numberTypes, _VersionTupleEnumMixin
__all__ = [
@@ -45,6 +45,8 @@ __all__ = [
"glyphNameToFileName"
]
+logger = logging.getLogger(__name__)
+
# ---------
# Constants
@@ -52,15 +54,35 @@ __all__ = [
CONTENTS_FILENAME = "contents.plist"
LAYERINFO_FILENAME = "layerinfo.plist"
-supportedUFOFormatVersions = [1, 2, 3]
-supportedGLIFFormatVersions = [1, 2]
+
+
+class GLIFFormatVersion(tuple, _VersionTupleEnumMixin, enum.Enum):
+ FORMAT_1_0 = (1, 0)
+ FORMAT_2_0 = (2, 0)
+
+ @classmethod
+ def default(cls, ufoFormatVersion=None):
+ if ufoFormatVersion is not None:
+ return max(cls.supported_versions(ufoFormatVersion))
+ return super().default()
+
+ @classmethod
+ def supported_versions(cls, ufoFormatVersion=None):
+ if ufoFormatVersion is None:
+ # if ufo format unspecified, return all the supported GLIF formats
+ return super().supported_versions()
+ # else only return the GLIF formats supported by the given UFO format
+ versions = {cls.FORMAT_1_0}
+ if ufoFormatVersion >= UFOFormatVersion.FORMAT_3_0:
+ versions.add(cls.FORMAT_2_0)
+ return frozenset(versions)
# ------------
# Simple Glyph
# ------------
-class Glyph(object):
+class Glyph:
"""
Minimal glyph object. It has no glyph attributes until either
@@ -110,9 +132,10 @@ class GlyphSet(_UFOBaseIO):
self,
path,
glyphNameToFileNameFunc=None,
- ufoFormatVersion=3,
+ ufoFormatVersion=None,
validateRead=True,
validateWrite=True,
+ expectContentsFile=False,
):
"""
'path' should be a path (string) to an existing local directory, or
@@ -126,10 +149,23 @@ class GlyphSet(_UFOBaseIO):
``validateRead`` will validate read operations. Its default is ``True``.
``validateWrite`` will validate write operations. Its default is ``True``.
+ ``expectContentsFile`` will raise a GlifLibError if a contents.plist file is
+ not found on the glyph set file system. This should be set to ``True`` if you
+ are reading an existing UFO and ``False`` if you create a fresh glyph set.
"""
- if ufoFormatVersion not in supportedUFOFormatVersions:
- raise GlifLibError("Unsupported UFO format version: %s" % ufoFormatVersion)
- if isinstance(path, basestring):
+ try:
+ ufoFormatVersion = UFOFormatVersion(ufoFormatVersion)
+ except ValueError as e:
+ from fontTools.ufoLib.errors import UnsupportedUFOFormat
+
+ raise UnsupportedUFOFormat(
+ f"Unsupported UFO format: {ufoFormatVersion!r}"
+ ) from e
+
+ if hasattr(path, "__fspath__"): # support os.PathLike objects
+ path = path.__fspath__()
+
+ if isinstance(path, str):
try:
filesystem = fs.osfs.OSFS(path)
except fs.errors.CreateFailed:
@@ -151,7 +187,7 @@ class GlyphSet(_UFOBaseIO):
path = filesystem.getsyspath("/")
except fs.errors.NoSysPath:
# network or in-memory FS may not map to the local one
- path = unicode(filesystem)
+ path = str(filesystem)
# 'dirName' is kept for backward compatibility only, but it's DEPRECATED
# as it's not guaranteed that it maps to an existing OSFS directory.
# Client could use the FS api via the `self.fs` attribute instead.
@@ -159,7 +195,11 @@ class GlyphSet(_UFOBaseIO):
self.fs = filesystem
# if glyphSet contains no 'contents.plist', we consider it empty
self._havePreviousFile = filesystem.exists(CONTENTS_FILENAME)
- self.ufoFormatVersion = ufoFormatVersion
+ if expectContentsFile and not self._havePreviousFile:
+ raise GlifLibError(f"{CONTENTS_FILENAME} is missing.")
+ # attribute kept for backward compatibility
+ self.ufoFormatVersion = ufoFormatVersion.major
+ self.ufoFormatVersionTuple = ufoFormatVersion
if glyphNameToFileNameFunc is None:
glyphNameToFileNameFunc = glyphNameToFileName
self.glyphNameToFileName = glyphNameToFileNameFunc
@@ -187,9 +227,9 @@ class GlyphSet(_UFOBaseIO):
invalidFormat = True
else:
for name, fileName in contents.items():
- if not isinstance(name, basestring):
+ if not isinstance(name, str):
invalidFormat = True
- if not isinstance(fileName, basestring):
+ if not isinstance(fileName, str):
invalidFormat = True
elif not self.fs.exists(fileName):
raise GlifLibError(
@@ -253,8 +293,10 @@ class GlyphSet(_UFOBaseIO):
"""
if validateWrite is None:
validateWrite = self._validateWrite
- if self.ufoFormatVersion < 3:
- raise GlifLibError("layerinfo.plist is not allowed in UFO %d." % self.ufoFormatVersion)
+ if self.ufoFormatVersionTuple.major < 3:
+ raise GlifLibError(
+ "layerinfo.plist is not allowed in UFO %d." % self.ufoFormatVersionTuple.major
+ )
# gather data
infoData = {}
for attr in layerInfoVersion3ValueData.keys():
@@ -348,10 +390,7 @@ class GlyphSet(_UFOBaseIO):
validate = self._validateRead
text = self.getGLIF(glyphName)
tree = _glifTreeFromString(text)
- if self.ufoFormatVersion < 3:
- formatVersions = (1,)
- else:
- formatVersions = (1, 2)
+ formatVersions = GLIFFormatVersion.supported_versions(self.ufoFormatVersionTuple)
_readGlyphFromTree(tree, glyphObject, pointPen, formatVersions=formatVersions, validate=validate)
def writeGlyph(self, glyphName, glyphObject=None, drawPointsFunc=None, formatVersion=None, validate=None):
@@ -381,21 +420,35 @@ class GlyphSet(_UFOBaseIO):
The GLIF format version will be chosen based on the ufoFormatVersion
passed during the creation of this object. If a particular format
version is desired, it can be passed with the formatVersion argument.
+ The formatVersion argument accepts either a tuple of integers for
+ (major, minor), or a single integer for the major digit only (with
+ minor digit implied as 0).
+
+ An UnsupportedGLIFFormat exception is raised if the requested GLIF
+ formatVersion is not supported.
``validate`` will validate the data, by default it is set to the
class's ``validateWrite`` value, can be overridden.
"""
if formatVersion is None:
- if self.ufoFormatVersion >= 3:
- formatVersion = 2
- else:
- formatVersion = 1
- if formatVersion not in supportedGLIFFormatVersions:
- raise GlifLibError("Unsupported GLIF format version: %s" % formatVersion)
- if formatVersion == 2 and self.ufoFormatVersion < 3:
- raise GlifLibError(
- "Unsupported GLIF format version (%d) for UFO format version %d."
- % (formatVersion, self.ufoFormatVersion)
+ formatVersion = GLIFFormatVersion.default(self.ufoFormatVersionTuple)
+ else:
+ try:
+ formatVersion = GLIFFormatVersion(formatVersion)
+ except ValueError as e:
+ from fontTools.ufoLib.errors import UnsupportedGLIFFormat
+
+ raise UnsupportedGLIFFormat(
+ f"Unsupported GLIF format version: {formatVersion!r}"
+ ) from e
+ if formatVersion not in GLIFFormatVersion.supported_versions(
+ self.ufoFormatVersionTuple
+ ):
+ from fontTools.ufoLib.errors import UnsupportedGLIFFormat
+
+ raise UnsupportedGLIFFormat(
+ f"Unsupported GLIF format version ({formatVersion!s}) "
+ f"for UFO format version {self.ufoFormatVersionTuple!s}."
)
if validate is None:
validate = self._validateWrite
@@ -405,7 +458,7 @@ class GlyphSet(_UFOBaseIO):
self._existingFileNames = {}
for fileName in self.contents.values():
self._existingFileNames[fileName] = fileName.lower()
- fileName = self.glyphNameToFileName(glyphName, self._existingFileNames)
+ fileName = self.glyphNameToFileName(glyphName, self._existingFileNames.values())
self.contents[glyphName] = fileName
self._existingFileNames[fileName] = fileName.lower()
if self._reverseContents is not None:
@@ -523,19 +576,19 @@ def glyphNameToFileName(glyphName, existingFileNames):
"""
if existingFileNames is None:
existingFileNames = []
- if not isinstance(glyphName, unicode):
- try:
- new = unicode(glyphName)
- glyphName = new
- except UnicodeDecodeError:
- pass
return userNameToFileName(glyphName, existing=existingFileNames, suffix=".glif")
# -----------------------
# GLIF To and From String
# -----------------------
-def readGlyphFromString(aString, glyphObject=None, pointPen=None, formatVersions=(1, 2), validate=True):
+def readGlyphFromString(
+ aString,
+ glyphObject=None,
+ pointPen=None,
+ formatVersions=None,
+ validate=True,
+):
"""
Read .glif data from a string into a glyph object.
@@ -564,25 +617,65 @@ def readGlyphFromString(aString, glyphObject=None, pointPen=None, formatVersions
conforming to the PointPen protocol as the 'pointPen' argument.
This argument may be None if you don't need the outline data.
- The formatVersions argument defined the GLIF format versions
+ The formatVersions optional argument define the GLIF format versions
that are allowed to be read.
+ The type is Optional[Iterable[Tuple[int, int], int]]. It can contain
+ either integers (for the major versions to be allowed, with minor
+ digits defaulting to 0), or tuples of integers to specify both
+ (major, minor) versions.
+ By default when formatVersions is None all the GLIF format versions
+ currently defined are allowed to be read.
``validate`` will validate the read data. It is set to ``True`` by default.
"""
tree = _glifTreeFromString(aString)
- _readGlyphFromTree(tree, glyphObject, pointPen, formatVersions=formatVersions, validate=validate)
+
+ if formatVersions is None:
+ validFormatVersions = GLIFFormatVersion.supported_versions()
+ else:
+ validFormatVersions, invalidFormatVersions = set(), set()
+ for v in formatVersions:
+ try:
+ formatVersion = GLIFFormatVersion(v)
+ except ValueError:
+ invalidFormatVersions.add(v)
+ else:
+ validFormatVersions.add(formatVersion)
+ if not validFormatVersions:
+ raise ValueError(
+ "None of the requested GLIF formatVersions are supported: "
+ f"{formatVersions!r}"
+ )
+
+ _readGlyphFromTree(
+ tree, glyphObject, pointPen, formatVersions=validFormatVersions, validate=validate
+ )
def _writeGlyphToBytes(
- glyphName, glyphObject=None, drawPointsFunc=None, writer=None,
- formatVersion=2, validate=True):
+ glyphName,
+ glyphObject=None,
+ drawPointsFunc=None,
+ writer=None,
+ formatVersion=None,
+ validate=True,
+):
"""Return .glif data for a glyph as a UTF-8 encoded bytes string."""
+ try:
+ formatVersion = GLIFFormatVersion(formatVersion)
+ except ValueError:
+ from fontTools.ufoLib.errors import UnsupportedGLIFFormat
+
+ raise UnsupportedGLIFFormat("Unsupported GLIF format version: {formatVersion!r}")
# start
- if validate and not isinstance(glyphName, basestring):
+ if validate and not isinstance(glyphName, str):
raise GlifLibError("The glyph name is not properly formatted.")
if validate and len(glyphName) == 0:
raise GlifLibError("The glyph name is empty.")
- root = etree.Element("glyph", OrderedDict([("name", glyphName), ("format", repr(formatVersion))]))
+ glyphAttrs = OrderedDict([("name", glyphName), ("format", repr(formatVersion.major))])
+ if formatVersion.minor != 0:
+ glyphAttrs["formatMinor"] = repr(formatVersion.minor)
+ root = etree.Element("glyph", glyphAttrs)
identifiers = set()
# advance
_writeAdvance(glyphObject, root, validate)
@@ -593,21 +686,21 @@ def _writeGlyphToBytes(
if getattr(glyphObject, "note", None):
_writeNote(glyphObject, root, validate)
# image
- if formatVersion >= 2 and getattr(glyphObject, "image", None):
+ if formatVersion.major >= 2 and getattr(glyphObject, "image", None):
_writeImage(glyphObject, root, validate)
# guidelines
- if formatVersion >= 2 and getattr(glyphObject, "guidelines", None):
+ if formatVersion.major >= 2 and getattr(glyphObject, "guidelines", None):
_writeGuidelines(glyphObject, root, identifiers, validate)
# anchors
anchors = getattr(glyphObject, "anchors", None)
- if formatVersion >= 2 and anchors:
+ if formatVersion.major >= 2 and anchors:
_writeAnchors(glyphObject, root, identifiers, validate)
# outline
if drawPointsFunc is not None:
outline = etree.SubElement(root, "outline")
pen = GLIFPointPen(outline, identifiers=identifiers, validate=validate)
drawPointsFunc(pen)
- if formatVersion == 1 and anchors:
+ if formatVersion.major == 1 and anchors:
_writeAnchorsFormat1(pen, anchors, validate)
# prevent lxml from writing self-closing tags
if not len(outline):
@@ -622,10 +715,16 @@ def _writeGlyphToBytes(
return data
-def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, formatVersion=2, validate=True):
+def writeGlyphToString(
+ glyphName,
+ glyphObject=None,
+ drawPointsFunc=None,
+ formatVersion=None,
+ validate=True,
+):
"""
- Return .glif data for a glyph as a Unicode string (`unicode` in py2, `str`
- in py3). The XML declaration's encoding is always set to "UTF-8".
+ Return .glif data for a glyph as a string. The XML declaration's
+ encoding is always set to "UTF-8".
The 'glyphObject' argument can be any kind of object (even None);
the writeGlyphToString() method will attempt to get the following
attributes from it:
@@ -648,6 +747,13 @@ def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, formatV
proper PointPen methods to transfer the outline to the .glif file.
The GLIF format version can be specified with the formatVersion argument.
+ This accepts either a tuple of integers for (major, minor), or a single
+ integer for the major digit only (with minor digit implied as 0).
+ By default when formatVesion is None the latest GLIF format version will
+ be used; currently it's 2.0, which is equivalent to formatVersion=(2, 0).
+
+ An UnsupportedGLIFFormat exception is raised if the requested UFO
+ formatVersion is not supported.
``validate`` will validate the written data. It is set to ``True`` by default.
"""
@@ -683,11 +789,11 @@ def _writeAdvance(glyphObject, element, validate):
def _writeUnicodes(glyphObject, element, validate):
unicodes = getattr(glyphObject, "unicodes", None)
- if validate and isinstance(unicodes, integerTypes):
+ if validate and isinstance(unicodes, int):
unicodes = [unicodes]
seen = set()
for code in unicodes:
- if validate and not isinstance(code, integerTypes):
+ if validate and not isinstance(code, int):
raise GlifLibError("unicode values must be int")
if code in seen:
continue
@@ -697,12 +803,11 @@ def _writeUnicodes(glyphObject, element, validate):
def _writeNote(glyphObject, element, validate):
note = getattr(glyphObject, "note", None)
- if validate and not isinstance(note, basestring):
- raise GlifLibError("note attribute must be str or unicode")
+ if validate and not isinstance(note, str):
+ raise GlifLibError("note attribute must be str")
note = note.strip()
note = "\n" + note + "\n"
- # ensure text is unicode, if it's bytes decode as ASCII
- etree.SubElement(element, "note").text = tounicode(note)
+ etree.SubElement(element, "note").text = note
def _writeImage(glyphObject, element, validate):
image = getattr(glyphObject, "image", None)
@@ -807,7 +912,7 @@ def _writeLib(glyphObject, element, validate):
# -----------------------
layerInfoVersion3ValueData = {
- "color" : dict(type=basestring, valueValidator=colorValidator),
+ "color" : dict(type=str, valueValidator=colorValidator),
"lib" : dict(type=dict, valueValidator=genericTypeValidator)
}
@@ -853,7 +958,7 @@ def validateLayerInfoVersion3Data(infoData):
raise GlifLibError("Unknown attribute %s." % attr)
isValidValue = validateLayerInfoVersion3ValueForAttribute(attr, value)
if not isValidValue:
- raise GlifLibError("Invalid value for attribute %s (%s)." % (attr, repr(value)))
+ raise GlifLibError(f"Invalid value for attribute {attr} ({value!r}).")
return infoData
# -----------------
@@ -861,7 +966,11 @@ def validateLayerInfoVersion3Data(infoData):
# -----------------
def _glifTreeFromFile(aFile):
- root = etree.parse(aFile).getroot()
+ if etree._have_lxml:
+ tree = etree.parse(aFile, parser=etree.XMLParser(remove_comments=True))
+ else:
+ tree = etree.parse(aFile)
+ root = tree.getroot()
if root.tag != "glyph":
raise GlifLibError("The GLIF is not properly formatted.")
if root.text and root.text.strip() != '':
@@ -871,34 +980,64 @@ def _glifTreeFromFile(aFile):
def _glifTreeFromString(aString):
data = tobytes(aString, encoding="utf-8")
- root = etree.fromstring(data)
+ if etree._have_lxml:
+ root = etree.fromstring(data, parser=etree.XMLParser(remove_comments=True))
+ else:
+ root = etree.fromstring(data)
if root.tag != "glyph":
raise GlifLibError("The GLIF is not properly formatted.")
if root.text and root.text.strip() != '':
raise GlifLibError("Invalid GLIF structure.")
return root
-def _readGlyphFromTree(tree, glyphObject=None, pointPen=None, formatVersions=(1, 2), validate=True):
+
+def _readGlyphFromTree(
+ tree,
+ glyphObject=None,
+ pointPen=None,
+ formatVersions=GLIFFormatVersion.supported_versions(),
+ validate=True,
+):
# check the format version
- formatVersion = tree.get("format")
- if validate and formatVersion is None:
+ formatVersionMajor = tree.get("format")
+ if validate and formatVersionMajor is None:
raise GlifLibError("Unspecified format version in GLIF.")
+ formatVersionMinor = tree.get("formatMinor", 0)
try:
- v = int(formatVersion)
- formatVersion = v
- except ValueError:
- pass
+ formatVersion = GLIFFormatVersion((int(formatVersionMajor), int(formatVersionMinor)))
+ except ValueError as e:
+ msg = "Unsupported GLIF format: %s.%s" % (formatVersionMajor, formatVersionMinor)
+ if validate:
+ from fontTools.ufoLib.errors import UnsupportedGLIFFormat
+
+ raise UnsupportedGLIFFormat(msg) from e
+ # warn but continue using the latest supported format
+ formatVersion = GLIFFormatVersion.default()
+ logger.warning(
+ "%s. Assuming the latest supported version (%s). "
+ "Some data may be skipped or parsed incorrectly.",
+ msg,
+ formatVersion,
+ )
+
if validate and formatVersion not in formatVersions:
- raise GlifLibError("Forbidden GLIF format version: %s" % formatVersion)
- if formatVersion == 1:
- _readGlyphFromTreeFormat1(tree=tree, glyphObject=glyphObject, pointPen=pointPen, validate=validate)
- elif formatVersion == 2:
- _readGlyphFromTreeFormat2(tree=tree, glyphObject=glyphObject, pointPen=pointPen, validate=validate)
- else:
- raise GlifLibError("Unsupported GLIF format version: %s" % formatVersion)
+ raise GlifLibError(f"Forbidden GLIF format version: {formatVersion!s}")
+
+ try:
+ readGlyphFromTree = _READ_GLYPH_FROM_TREE_FUNCS[formatVersion]
+ except KeyError:
+ raise NotImplementedError(formatVersion)
+ readGlyphFromTree(
+ tree=tree,
+ glyphObject=glyphObject,
+ pointPen=pointPen,
+ validate=validate,
+ formatMinor=formatVersion.minor,
+ )
-def _readGlyphFromTreeFormat1(tree, glyphObject=None, pointPen=None, validate=None):
+
+def _readGlyphFromTreeFormat1(tree, glyphObject=None, pointPen=None, validate=None, **kwargs):
# get the name
_readName(glyphObject, tree, validate)
# populate the sub elements
@@ -946,7 +1085,9 @@ def _readGlyphFromTreeFormat1(tree, glyphObject=None, pointPen=None, validate=No
if unicodes:
_relaxedSetattr(glyphObject, "unicodes", unicodes)
-def _readGlyphFromTreeFormat2(tree, glyphObject=None, pointPen=None, validate=None):
+def _readGlyphFromTreeFormat2(
+ tree, glyphObject=None, pointPen=None, validate=None, formatMinor=0
+):
# get the name
_readName(glyphObject, tree, validate)
# populate the sub elements
@@ -1032,6 +1173,13 @@ def _readGlyphFromTreeFormat2(tree, glyphObject=None, pointPen=None, validate=No
raise GlifLibError("The anchors are improperly formatted.")
_relaxedSetattr(glyphObject, "anchors", anchors)
+
+_READ_GLYPH_FROM_TREE_FUNCS = {
+ GLIFFormatVersion.FORMAT_1_0: _readGlyphFromTreeFormat1,
+ GLIFFormatVersion.FORMAT_2_0: _readGlyphFromTreeFormat2,
+}
+
+
def _readName(glyphObject, root, validate):
glyphName = root.get("name")
if validate and not glyphName:
@@ -1073,13 +1221,13 @@ def _readImage(glyphObject, image, validate):
# GLIF to PointPen
# ----------------
-contourAttributesFormat2 = set(["identifier"])
-componentAttributesFormat1 = set(["base", "xScale", "xyScale", "yxScale", "yScale", "xOffset", "yOffset"])
-componentAttributesFormat2 = componentAttributesFormat1 | set(["identifier"])
-pointAttributesFormat1 = set(["x", "y", "type", "smooth", "name"])
-pointAttributesFormat2 = pointAttributesFormat1 | set(["identifier"])
-pointSmoothOptions = set(("no", "yes"))
-pointTypeOptions = set(["move", "line", "offcurve", "curve", "qcurve"])
+contourAttributesFormat2 = {"identifier"}
+componentAttributesFormat1 = {"base", "xScale", "xyScale", "yxScale", "yScale", "xOffset", "yOffset"}
+componentAttributesFormat2 = componentAttributesFormat1 | {"identifier"}
+pointAttributesFormat1 = {"x", "y", "type", "smooth", "name"}
+pointAttributesFormat2 = pointAttributesFormat1 | {"identifier"}
+pointSmoothOptions = {"no", "yes"}
+pointTypeOptions = {"move", "line", "offcurve", "curve", "qcurve"}
# format 1
@@ -1274,10 +1422,10 @@ def _validateAndMassagePointStructures(contour, pointAttributes, openContourOffC
raise GlifLibError("Unknown child elements in point element.")
# x and y are required
for attr in ("x", "y"):
- value = element.get(attr)
- if validate and value is None:
- raise GlifLibError("Required %s attribute is missing in point element." % attr)
- point[attr] = _number(value)
+ try:
+ point[attr] = _number(point[attr])
+ except KeyError as e:
+ raise GlifLibError(f"Required {attr} attribute is missing in point element.") from e
# segment type
pointType = point.pop("type", "offcurve")
if validate and pointType not in pointTypeOptions:
@@ -1389,7 +1537,7 @@ def _number(s):
class _DoneParsing(Exception): pass
-class _BaseParser(object):
+class _BaseParser:
def __init__(self):
self._elementStack = []
@@ -1423,7 +1571,7 @@ class _FetchUnicodesParser(_BaseParser):
def __init__(self):
self.unicodes = []
- super(_FetchUnicodesParser, self).__init__()
+ super().__init__()
def startElementHandler(self, name, attrs):
if name == "unicode" and self._elementStack and self._elementStack[-1] == "glyph":
@@ -1435,7 +1583,7 @@ class _FetchUnicodesParser(_BaseParser):
self.unicodes.append(value)
except ValueError:
pass
- super(_FetchUnicodesParser, self).startElementHandler(name, attrs)
+ super().startElementHandler(name, attrs)
# image
@@ -1454,13 +1602,13 @@ class _FetchImageFileNameParser(_BaseParser):
def __init__(self):
self.fileName = None
- super(_FetchImageFileNameParser, self).__init__()
+ super().__init__()
def startElementHandler(self, name, attrs):
if name == "image" and self._elementStack and self._elementStack[-1] == "glyph":
self.fileName = attrs.get("fileName")
raise _DoneParsing
- super(_FetchImageFileNameParser, self).startElementHandler(name, attrs)
+ super().startElementHandler(name, attrs)
# component references
@@ -1479,19 +1627,19 @@ class _FetchComponentBasesParser(_BaseParser):
def __init__(self):
self.bases = []
- super(_FetchComponentBasesParser, self).__init__()
+ super().__init__()
def startElementHandler(self, name, attrs):
if name == "component" and self._elementStack and self._elementStack[-1] == "outline":
base = attrs.get("base")
if base is not None:
self.bases.append(base)
- super(_FetchComponentBasesParser, self).startElementHandler(name, attrs)
+ super().startElementHandler(name, attrs)
def endElementHandler(self, name):
if name == "outline":
raise _DoneParsing
- super(_FetchComponentBasesParser, self).endElementHandler(name)
+ super().endElementHandler(name)
# --------------
# GLIF Point Pen
@@ -1514,10 +1662,10 @@ class GLIFPointPen(AbstractPointPen):
part of .glif files.
"""
- def __init__(self, element, formatVersion=2, identifiers=None, validate=True):
+ def __init__(self, element, formatVersion=None, identifiers=None, validate=True):
if identifiers is None:
identifiers = set()
- self.formatVersion = formatVersion
+ self.formatVersion = GLIFFormatVersion(formatVersion)
self.identifiers = identifiers
self.outline = element
self.contour = None
@@ -1527,7 +1675,7 @@ class GLIFPointPen(AbstractPointPen):
def beginPath(self, identifier=None, **kwargs):
attrs = OrderedDict()
- if identifier is not None and self.formatVersion >= 2:
+ if identifier is not None and self.formatVersion.major >= 2:
if self.validate:
if identifier in self.identifiers:
raise GlifLibError("identifier used more than once: %s" % identifier)
@@ -1588,7 +1736,7 @@ class GLIFPointPen(AbstractPointPen):
if name is not None:
attrs["name"] = name
# identifier
- if identifier is not None and self.formatVersion >= 2:
+ if identifier is not None and self.formatVersion.major >= 2:
if self.validate:
if identifier in self.identifiers:
raise GlifLibError("identifier used more than once: %s" % identifier)
@@ -1605,7 +1753,7 @@ class GLIFPointPen(AbstractPointPen):
raise GlifLibError("transformation values must be int or float")
if value != default:
attrs[attr] = repr(value)
- if identifier is not None and self.formatVersion >= 2:
+ if identifier is not None and self.formatVersion.major >= 2:
if self.validate:
if identifier in self.identifiers:
raise GlifLibError("identifier used more than once: %s" % identifier)
diff --git a/Lib/fontTools/ufoLib/kerning.py b/Lib/fontTools/ufoLib/kerning.py
index 03e413fa..947222a4 100644
--- a/Lib/fontTools/ufoLib/kerning.py
+++ b/Lib/fontTools/ufoLib/kerning.py
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, unicode_literals
def lookupKerningValue(pair, kerning, groups, fallback=0, glyphToFirstGroup=None, glyphToSecondGroup=None):
diff --git a/Lib/fontTools/ufoLib/plistlib.py b/Lib/fontTools/ufoLib/plistlib.py
index d0247bf9..76381687 100644
--- a/Lib/fontTools/ufoLib/plistlib.py
+++ b/Lib/fontTools/ufoLib/plistlib.py
@@ -2,7 +2,8 @@
for the old ufoLib.plistlib module, which was moved to fontTools.misc.plistlib.
Please use the latter instead.
"""
-from fontTools.misc.plistlib import *
+from fontTools.misc.plistlib import dump, dumps, load, loads
+from fontTools.misc.py23 import tobytes
# The following functions were part of the old py2-like ufoLib.plistlib API.
# They are kept only for backward compatiblity.
@@ -12,7 +13,7 @@ from fontTools.ufoLib.utils import deprecated
@deprecated("Use 'fontTools.misc.plistlib.load' instead")
def readPlist(path_or_file):
did_open = False
- if isinstance(path_or_file, basestring):
+ if isinstance(path_or_file, str):
path_or_file = open(path_or_file, "rb")
did_open = True
try:
@@ -25,7 +26,7 @@ def readPlist(path_or_file):
@deprecated("Use 'fontTools.misc.plistlib.dump' instead")
def writePlist(value, path_or_file):
did_open = False
- if isinstance(path_or_file, basestring):
+ if isinstance(path_or_file, str):
path_or_file = open(path_or_file, "wb")
did_open = True
try:
diff --git a/Lib/fontTools/ufoLib/utils.py b/Lib/fontTools/ufoLib/utils.py
index 77e2f92a..85878b47 100644
--- a/Lib/fontTools/ufoLib/utils.py
+++ b/Lib/fontTools/ufoLib/utils.py
@@ -1,51 +1,11 @@
"""The module contains miscellaneous helpers.
It's not considered part of the public ufoLib API.
"""
-from __future__ import absolute_import, unicode_literals
-import sys
import warnings
import functools
-from datetime import datetime
-from fontTools.misc.py23 import tounicode
-if hasattr(datetime, "timestamp"): # python >= 3.3
-
- def datetimeAsTimestamp(dt):
- return dt.timestamp()
-
-else:
- from datetime import tzinfo, timedelta
-
- ZERO = timedelta(0)
-
- class UTC(tzinfo):
-
- def utcoffset(self, dt):
- return ZERO
-
- def tzname(self, dt):
- return "UTC"
-
- def dst(self, dt):
- return ZERO
-
- utc = UTC()
-
- EPOCH = datetime.fromtimestamp(0, tz=utc)
-
- def datetimeAsTimestamp(dt):
- return (dt - EPOCH).total_seconds()
-
-
-# TODO: should import from fontTools.misc.py23
-try:
- long = long
-except NameError:
- long = int
-
-integerTypes = (int, long)
-numberTypes = (int, float, long)
+numberTypes = (int, float)
def deprecated(msg=""):
@@ -65,7 +25,7 @@ def deprecated(msg=""):
@functools.wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(
- "{} function is a deprecated. {}".format(func.__name__, msg),
+ f"{func.__name__} function is a deprecated. {msg}",
category=DeprecationWarning,
stacklevel=2,
)
@@ -76,8 +36,37 @@ def deprecated(msg=""):
return deprecated_decorator
-def fsdecode(path, encoding=sys.getfilesystemencoding()):
- return tounicode(path, encoding=encoding)
+# To be mixed with enum.Enum in UFOFormatVersion and GLIFFormatVersion
+class _VersionTupleEnumMixin:
+ @property
+ def major(self):
+ return self.value[0]
+
+ @property
+ def minor(self):
+ return self.value[1]
+
+ @classmethod
+ def _missing_(cls, value):
+ # allow to initialize a version enum from a single (major) integer
+ if isinstance(value, int):
+ return cls((value, 0))
+ # or from None to obtain the current default version
+ if value is None:
+ return cls.default()
+ return super()._missing_(value)
+
+ def __str__(self):
+ return f"{self.major}.{self.minor}"
+
+ @classmethod
+ def default(cls):
+ # get the latest defined version (i.e. the max of all versions)
+ return max(cls.__members__.values())
+
+ @classmethod
+ def supported_versions(cls):
+ return frozenset(cls.__members__.values())
if __name__ == "__main__":
diff --git a/Lib/fontTools/ufoLib/validators.py b/Lib/fontTools/ufoLib/validators.py
index 844b7f1b..49cb0e49 100644
--- a/Lib/fontTools/ufoLib/validators.py
+++ b/Lib/fontTools/ufoLib/validators.py
@@ -1,18 +1,12 @@
"""Various low level data validators."""
-from __future__ import absolute_import, unicode_literals
import calendar
from io import open
import fs.base
import fs.osfs
-try:
- from collections.abc import Mapping # python >= 3.3
-except ImportError:
- from collections import Mapping
-
-from fontTools.misc.py23 import basestring
-from fontTools.ufoLib.utils import integerTypes, numberTypes
+from collections.abc import Mapping
+from fontTools.ufoLib.utils import numberTypes
# -------
@@ -48,7 +42,7 @@ def genericIntListValidator(values, validValues):
if valuesSet - validValuesSet:
return False
for value in values:
- if not isinstance(value, integerTypes):
+ if not isinstance(value, int):
return False
return True
@@ -56,7 +50,7 @@ def genericNonNegativeIntValidator(value):
"""
Generic. (Added at version 3.)
"""
- if not isinstance(value, integerTypes):
+ if not isinstance(value, int):
return False
if value < 0:
return False
@@ -143,7 +137,7 @@ def fontInfoOpenTypeHeadCreatedValidator(value):
Version 2+.
"""
# format: 0000/00/00 00:00:00
- if not isinstance(value, basestring):
+ if not isinstance(value, str):
return False
# basic formatting
if not len(value) == 19:
@@ -203,7 +197,7 @@ def fontInfoOpenTypeNameRecordsValidator(value):
"""
if not isinstance(value, list):
return False
- dictPrototype = dict(nameID=(int, True), platformID=(int, True), encodingID=(int, True), languageID=(int, True), string=(basestring, True))
+ dictPrototype = dict(nameID=(int, True), platformID=(int, True), encodingID=(int, True), languageID=(int, True), string=(str, True))
for nameRecord in value:
if not genericDictValidator(nameRecord, dictPrototype):
return False
@@ -213,7 +207,7 @@ def fontInfoOpenTypeOS2WeightClassValidator(value):
"""
Version 2+.
"""
- if not isinstance(value, integerTypes):
+ if not isinstance(value, int):
return False
if value < 0:
return False
@@ -223,7 +217,7 @@ def fontInfoOpenTypeOS2WidthClassValidator(value):
"""
Version 2+.
"""
- if not isinstance(value, integerTypes):
+ if not isinstance(value, int):
return False
if value < 1:
return False
@@ -240,7 +234,7 @@ def fontInfoVersion2OpenTypeOS2PanoseValidator(values):
if len(values) != 10:
return False
for value in values:
- if not isinstance(value, integerTypes):
+ if not isinstance(value, int):
return False
# XXX further validation?
return True
@@ -254,7 +248,7 @@ def fontInfoVersion3OpenTypeOS2PanoseValidator(values):
if len(values) != 10:
return False
for value in values:
- if not isinstance(value, integerTypes):
+ if not isinstance(value, int):
return False
if value < 0:
return False
@@ -270,7 +264,7 @@ def fontInfoOpenTypeOS2FamilyClassValidator(values):
if len(values) != 2:
return False
for value in values:
- if not isinstance(value, integerTypes):
+ if not isinstance(value, int):
return False
classID, subclassID = values
if classID < 0 or classID > 14:
@@ -335,7 +329,7 @@ def fontInfoWOFFMetadataUniqueIDValidator(value):
"""
Version 3+.
"""
- dictPrototype = dict(id=(basestring, True))
+ dictPrototype = dict(id=(str, True))
if not genericDictValidator(value, dictPrototype):
return False
return True
@@ -344,7 +338,7 @@ def fontInfoWOFFMetadataVendorValidator(value):
"""
Version 3+.
"""
- dictPrototype = {"name" : (basestring, True), "url" : (basestring, False), "dir" : (basestring, False), "class" : (basestring, False)}
+ dictPrototype = {"name" : (str, True), "url" : (str, False), "dir" : (str, False), "class" : (str, False)}
if not genericDictValidator(value, dictPrototype):
return False
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
@@ -360,7 +354,7 @@ def fontInfoWOFFMetadataCreditsValidator(value):
return False
if not len(value["credits"]):
return False
- dictPrototype = {"name" : (basestring, True), "url" : (basestring, False), "role" : (basestring, False), "dir" : (basestring, False), "class" : (basestring, False)}
+ dictPrototype = {"name" : (str, True), "url" : (str, False), "role" : (str, False), "dir" : (str, False), "class" : (str, False)}
for credit in value["credits"]:
if not genericDictValidator(credit, dictPrototype):
return False
@@ -372,7 +366,7 @@ def fontInfoWOFFMetadataDescriptionValidator(value):
"""
Version 3+.
"""
- dictPrototype = dict(url=(basestring, False), text=(list, True))
+ dictPrototype = dict(url=(str, False), text=(list, True))
if not genericDictValidator(value, dictPrototype):
return False
for text in value["text"]:
@@ -384,7 +378,7 @@ def fontInfoWOFFMetadataLicenseValidator(value):
"""
Version 3+.
"""
- dictPrototype = dict(url=(basestring, False), text=(list, False), id=(basestring, False))
+ dictPrototype = dict(url=(str, False), text=(list, False), id=(str, False))
if not genericDictValidator(value, dictPrototype):
return False
if "text" in value:
@@ -421,7 +415,7 @@ def fontInfoWOFFMetadataLicenseeValidator(value):
"""
Version 3+.
"""
- dictPrototype = {"name" : (basestring, True), "dir" : (basestring, False), "class" : (basestring, False)}
+ dictPrototype = {"name" : (str, True), "dir" : (str, False), "class" : (str, False)}
if not genericDictValidator(value, dictPrototype):
return False
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
@@ -432,7 +426,7 @@ def fontInfoWOFFMetadataTextValue(value):
"""
Version 3+.
"""
- dictPrototype = {"text" : (basestring, True), "language" : (basestring, False), "dir" : (basestring, False), "class" : (basestring, False)}
+ dictPrototype = {"text" : (str, True), "language" : (str, False), "dir" : (str, False), "class" : (str, False)}
if not genericDictValidator(value, dictPrototype):
return False
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
@@ -456,7 +450,7 @@ def fontInfoWOFFMetadataExtensionValidator(value):
"""
Version 3+.
"""
- dictPrototype = dict(names=(list, False), items=(list, True), id=(basestring, False))
+ dictPrototype = dict(names=(list, False), items=(list, True), id=(str, False))
if not genericDictValidator(value, dictPrototype):
return False
if "names" in value:
@@ -472,7 +466,7 @@ def fontInfoWOFFMetadataExtensionItemValidator(value):
"""
Version 3+.
"""
- dictPrototype = dict(id=(basestring, False), names=(list, True), values=(list, True))
+ dictPrototype = dict(id=(str, False), names=(list, True), values=(list, True))
if not genericDictValidator(value, dictPrototype):
return False
for name in value["names"]:
@@ -487,7 +481,7 @@ def fontInfoWOFFMetadataExtensionNameValidator(value):
"""
Version 3+.
"""
- dictPrototype = {"text" : (basestring, True), "language" : (basestring, False), "dir" : (basestring, False), "class" : (basestring, False)}
+ dictPrototype = {"text" : (str, True), "language" : (str, False), "dir" : (str, False), "class" : (str, False)}
if not genericDictValidator(value, dictPrototype):
return False
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
@@ -498,7 +492,7 @@ def fontInfoWOFFMetadataExtensionValueValidator(value):
"""
Version 3+.
"""
- dictPrototype = {"text" : (basestring, True), "language" : (basestring, False), "dir" : (basestring, False), "class" : (basestring, False)}
+ dictPrototype = {"text" : (str, True), "language" : (str, False), "dir" : (str, False), "class" : (str, False)}
if not genericDictValidator(value, dictPrototype):
return False
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
@@ -529,7 +523,7 @@ def guidelinesValidator(value, identifiers=None):
_guidelineDictPrototype = dict(
x=((int, float), False), y=((int, float), False), angle=((int, float), False),
- name=(basestring, False), color=(basestring, False), identifier=(basestring, False)
+ name=(str, False), color=(str, False), identifier=(str, False)
)
def guidelineValidator(value):
@@ -591,8 +585,8 @@ def anchorsValidator(value, identifiers=None):
_anchorDictPrototype = dict(
x=((int, float), False), y=((int, float), False),
- name=(basestring, False), color=(basestring, False),
- identifier=(basestring, False)
+ name=(str, False), color=(str, False),
+ identifier=(str, False)
)
def anchorValidator(value):
@@ -633,7 +627,7 @@ def identifierValidator(value):
"""
validCharactersMin = 0x20
validCharactersMax = 0x7E
- if not isinstance(value, basestring):
+ if not isinstance(value, str):
return False
if not value:
return False
@@ -692,7 +686,7 @@ def colorValidator(value):
>>> colorValidator("1, 1, 1, 1")
True
"""
- if not isinstance(value, basestring):
+ if not isinstance(value, str):
return False
parts = value.split(",")
if len(parts) != 4:
@@ -726,11 +720,11 @@ def colorValidator(value):
pngSignature = b"\x89PNG\r\n\x1a\n"
_imageDictPrototype = dict(
- fileName=(basestring, True),
+ fileName=(str, True),
xScale=((int, float), False), xyScale=((int, float), False),
yxScale=((int, float), False), yScale=((int, float), False),
xOffset=((int, float), False), yOffset=((int, float), False),
- color=(basestring, False)
+ color=(str, False)
)
def imageValidator(value):
@@ -797,7 +791,7 @@ def layerContentsValidator(value, ufoPathOrFileSystem):
if not len(entry) == 2:
return False, bogusFileMessage
for i in entry:
- if not isinstance(i, basestring):
+ if not isinstance(i, str):
return False, bogusFileMessage
layerName, directoryName = entry
# check directory naming
@@ -881,7 +875,7 @@ def groupsValidator(value):
firstSideMapping = {}
secondSideMapping = {}
for groupName, glyphList in value.items():
- if not isinstance(groupName, (basestring)):
+ if not isinstance(groupName, (str)):
return False, bogusFormatMessage
if not isinstance(glyphList, (list, tuple)):
return False, bogusFormatMessage
@@ -889,7 +883,7 @@ def groupsValidator(value):
return False, "A group has an empty name."
if groupName.startswith("public."):
if not groupName.startswith("public.kern1.") and not groupName.startswith("public.kern2."):
- # unknown pubic.* name. silently skip.
+ # unknown public.* name. silently skip.
continue
else:
if len("public.kernN.") == len(groupName):
@@ -899,7 +893,7 @@ def groupsValidator(value):
else:
d = secondSideMapping
for glyphName in glyphList:
- if not isinstance(glyphName, basestring):
+ if not isinstance(glyphName, str):
return False, "The group data %s contains an invalid member." % groupName
if glyphName in d:
return False, "The glyph \"%s\" occurs in too many kerning groups." % glyphName
@@ -937,12 +931,12 @@ def kerningValidator(data):
if not isinstance(data, Mapping):
return False, bogusFormatMessage
for first, secondDict in data.items():
- if not isinstance(first, basestring):
+ if not isinstance(first, str):
return False, bogusFormatMessage
elif not isinstance(secondDict, Mapping):
return False, bogusFormatMessage
for second, value in secondDict.items():
- if not isinstance(second, basestring):
+ if not isinstance(second, str):
return False, bogusFormatMessage
elif not isinstance(value, numberTypes):
return False, bogusFormatMessage
@@ -983,7 +977,7 @@ def fontLibValidator(value):
>>> valid
False
>>> print(msg)
- The lib key is not properly formatted: expected basestring, found int: 1
+ The lib key is not properly formatted: expected str, found int: 1
>>> lib = {"public.glyphOrder" : "hello"}
>>> valid, msg = fontLibValidator(lib)
@@ -997,15 +991,15 @@ def fontLibValidator(value):
>>> valid
False
>>> print(msg) # doctest: +ELLIPSIS
- public.glyphOrder is not properly formatted: expected basestring,...
+ public.glyphOrder is not properly formatted: expected str,...
"""
if not isDictEnough(value):
reason = "expected a dictionary, found %s" % type(value).__name__
return False, _bogusLibFormatMessage % reason
for key, value in value.items():
- if not isinstance(key, basestring):
+ if not isinstance(key, str):
return False, (
- "The lib key is not properly formatted: expected basestring, found %s: %r" %
+ "The lib key is not properly formatted: expected str, found %s: %r" %
(type(key).__name__, key))
# public.glyphOrder
if key == "public.glyphOrder":
@@ -1014,8 +1008,8 @@ def fontLibValidator(value):
reason = "expected list or tuple, found %s" % type(value).__name__
return False, bogusGlyphOrderMessage % reason
for glyphName in value:
- if not isinstance(glyphName, basestring):
- reason = "expected basestring, found %s" % type(glyphName).__name__
+ if not isinstance(glyphName, str):
+ reason = "expected str, found %s" % type(glyphName).__name__
return False, bogusGlyphOrderMessage % reason
return True, None
@@ -1051,7 +1045,7 @@ def glyphLibValidator(value):
reason = "expected a dictionary, found %s" % type(value).__name__
return False, _bogusLibFormatMessage % reason
for key, value in value.items():
- if not isinstance(key, basestring):
+ if not isinstance(key, str):
reason = "key (%s) should be a string" % key
return False, _bogusLibFormatMessage % reason
# public.markColor
diff --git a/Lib/fontTools/unicode.py b/Lib/fontTools/unicode.py
index ef3ed695..e0867aa1 100644
--- a/Lib/fontTools/unicode.py
+++ b/Lib/fontTools/unicode.py
@@ -1,8 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
def _makeunicodes(f):
- import re
lines = iter(f.readlines())
unicodes = {}
for line in lines:
@@ -17,7 +13,7 @@ def _makeunicodes(f):
class _UnicodeCustom(object):
def __init__(self, f):
- if isinstance(f, basestring):
+ if isinstance(f, str):
with open(f) as fd:
codes = _makeunicodes(fd)
else:
@@ -40,7 +36,7 @@ class _UnicodeBuiltin(object):
except ImportError:
import unicodedata
try:
- return unicodedata.name(unichr(charCode))
+ return unicodedata.name(chr(charCode))
except ValueError:
return "????"
diff --git a/Lib/fontTools/unicodedata/Blocks.py b/Lib/fontTools/unicodedata/Blocks.py
index 132b0954..0755074b 100644
--- a/Lib/fontTools/unicodedata/Blocks.py
+++ b/Lib/fontTools/unicodedata/Blocks.py
@@ -4,8 +4,8 @@
# Source: https://unicode.org/Public/UNIDATA/Blocks.txt
# License: http://unicode.org/copyright.html#License
#
-# Blocks-12.1.0.txt
-# Date: 2019-03-08, 23:59:00 GMT [KW]
+# Blocks-13.0.0.txt
+# Date: 2019-07-10, 19:06:00 GMT [KW]
# © 2019 Unicode®, Inc.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
@@ -234,10 +234,12 @@ RANGES = [
0x10D00, # .. 0x10D3F ; Hanifi Rohingya
0x10D40, # .. 0x10E5F ; No_Block
0x10E60, # .. 0x10E7F ; Rumi Numeral Symbols
- 0x10E80, # .. 0x10EFF ; No_Block
+ 0x10E80, # .. 0x10EBF ; Yezidi
+ 0x10EC0, # .. 0x10EFF ; No_Block
0x10F00, # .. 0x10F2F ; Old Sogdian
0x10F30, # .. 0x10F6F ; Sogdian
- 0x10F70, # .. 0x10FDF ; No_Block
+ 0x10F70, # .. 0x10FAF ; No_Block
+ 0x10FB0, # .. 0x10FDF ; Chorasmian
0x10FE0, # .. 0x10FFF ; Elymaic
0x11000, # .. 0x1107F ; Brahmi
0x11080, # .. 0x110CF ; Kaithi
@@ -265,7 +267,8 @@ RANGES = [
0x11800, # .. 0x1184F ; Dogra
0x11850, # .. 0x1189F ; No_Block
0x118A0, # .. 0x118FF ; Warang Citi
- 0x11900, # .. 0x1199F ; No_Block
+ 0x11900, # .. 0x1195F ; Dives Akuru
+ 0x11960, # .. 0x1199F ; No_Block
0x119A0, # .. 0x119FF ; Nandinagari
0x11A00, # .. 0x11A4F ; Zanabazar Square
0x11A50, # .. 0x11AAF ; Soyombo
@@ -279,7 +282,8 @@ RANGES = [
0x11D60, # .. 0x11DAF ; Gunjala Gondi
0x11DB0, # .. 0x11EDF ; No_Block
0x11EE0, # .. 0x11EFF ; Makasar
- 0x11F00, # .. 0x11FBF ; No_Block
+ 0x11F00, # .. 0x11FAF ; No_Block
+ 0x11FB0, # .. 0x11FBF ; Lisu Supplement
0x11FC0, # .. 0x11FFF ; Tamil Supplement
0x12000, # .. 0x123FF ; Cuneiform
0x12400, # .. 0x1247F ; Cuneiform Numbers and Punctuation
@@ -303,7 +307,9 @@ RANGES = [
0x16FE0, # .. 0x16FFF ; Ideographic Symbols and Punctuation
0x17000, # .. 0x187FF ; Tangut
0x18800, # .. 0x18AFF ; Tangut Components
- 0x18B00, # .. 0x1AFFF ; No_Block
+ 0x18B00, # .. 0x18CFF ; Khitan Small Script
+ 0x18D00, # .. 0x18D8F ; Tangut Supplement
+ 0x18D90, # .. 0x1AFFF ; No_Block
0x1B000, # .. 0x1B0FF ; Kana Supplement
0x1B100, # .. 0x1B12F ; Kana Extended-A
0x1B130, # .. 0x1B16F ; Small Kana Extension
@@ -354,7 +360,8 @@ RANGES = [
0x1F900, # .. 0x1F9FF ; Supplemental Symbols and Pictographs
0x1FA00, # .. 0x1FA6F ; Chess Symbols
0x1FA70, # .. 0x1FAFF ; Symbols and Pictographs Extended-A
- 0x1FB00, # .. 0x1FFFF ; No_Block
+ 0x1FB00, # .. 0x1FBFF ; Symbols for Legacy Computing
+ 0x1FC00, # .. 0x1FFFF ; No_Block
0x20000, # .. 0x2A6DF ; CJK Unified Ideographs Extension B
0x2A6E0, # .. 0x2A6FF ; No_Block
0x2A700, # .. 0x2B73F ; CJK Unified Ideographs Extension C
@@ -363,7 +370,9 @@ RANGES = [
0x2CEB0, # .. 0x2EBEF ; CJK Unified Ideographs Extension F
0x2EBF0, # .. 0x2F7FF ; No_Block
0x2F800, # .. 0x2FA1F ; CJK Compatibility Ideographs Supplement
- 0x2FA20, # .. 0xDFFFF ; No_Block
+ 0x2FA20, # .. 0x2FFFF ; No_Block
+ 0x30000, # .. 0x3134F ; CJK Unified Ideographs Extension G
+ 0x31350, # .. 0xDFFFF ; No_Block
0xE0000, # .. 0xE007F ; Tags
0xE0080, # .. 0xE00FF ; No_Block
0xE0100, # .. 0xE01EF ; Variation Selectors Supplement
@@ -590,10 +599,12 @@ VALUES = [
'Hanifi Rohingya', # 10D00..10D3F
'No_Block', # 10D40..10E5F
'Rumi Numeral Symbols', # 10E60..10E7F
- 'No_Block', # 10E80..10EFF
+ 'Yezidi', # 10E80..10EBF
+ 'No_Block', # 10EC0..10EFF
'Old Sogdian', # 10F00..10F2F
'Sogdian', # 10F30..10F6F
- 'No_Block', # 10F70..10FDF
+ 'No_Block', # 10F70..10FAF
+ 'Chorasmian', # 10FB0..10FDF
'Elymaic', # 10FE0..10FFF
'Brahmi', # 11000..1107F
'Kaithi', # 11080..110CF
@@ -621,7 +632,8 @@ VALUES = [
'Dogra', # 11800..1184F
'No_Block', # 11850..1189F
'Warang Citi', # 118A0..118FF
- 'No_Block', # 11900..1199F
+ 'Dives Akuru', # 11900..1195F
+ 'No_Block', # 11960..1199F
'Nandinagari', # 119A0..119FF
'Zanabazar Square', # 11A00..11A4F
'Soyombo', # 11A50..11AAF
@@ -635,7 +647,8 @@ VALUES = [
'Gunjala Gondi', # 11D60..11DAF
'No_Block', # 11DB0..11EDF
'Makasar', # 11EE0..11EFF
- 'No_Block', # 11F00..11FBF
+ 'No_Block', # 11F00..11FAF
+ 'Lisu Supplement', # 11FB0..11FBF
'Tamil Supplement', # 11FC0..11FFF
'Cuneiform', # 12000..123FF
'Cuneiform Numbers and Punctuation', # 12400..1247F
@@ -659,7 +672,9 @@ VALUES = [
'Ideographic Symbols and Punctuation', # 16FE0..16FFF
'Tangut', # 17000..187FF
'Tangut Components', # 18800..18AFF
- 'No_Block', # 18B00..1AFFF
+ 'Khitan Small Script', # 18B00..18CFF
+ 'Tangut Supplement', # 18D00..18D8F
+ 'No_Block', # 18D90..1AFFF
'Kana Supplement', # 1B000..1B0FF
'Kana Extended-A', # 1B100..1B12F
'Small Kana Extension', # 1B130..1B16F
@@ -710,7 +725,8 @@ VALUES = [
'Supplemental Symbols and Pictographs', # 1F900..1F9FF
'Chess Symbols', # 1FA00..1FA6F
'Symbols and Pictographs Extended-A', # 1FA70..1FAFF
- 'No_Block', # 1FB00..1FFFF
+ 'Symbols for Legacy Computing', # 1FB00..1FBFF
+ 'No_Block', # 1FC00..1FFFF
'CJK Unified Ideographs Extension B', # 20000..2A6DF
'No_Block', # 2A6E0..2A6FF
'CJK Unified Ideographs Extension C', # 2A700..2B73F
@@ -719,7 +735,9 @@ VALUES = [
'CJK Unified Ideographs Extension F', # 2CEB0..2EBEF
'No_Block', # 2EBF0..2F7FF
'CJK Compatibility Ideographs Supplement', # 2F800..2FA1F
- 'No_Block', # 2FA20..DFFFF
+ 'No_Block', # 2FA20..2FFFF
+ 'CJK Unified Ideographs Extension G', # 30000..3134F
+ 'No_Block', # 31350..DFFFF
'Tags', # E0000..E007F
'No_Block', # E0080..E00FF
'Variation Selectors Supplement', # E0100..E01EF
diff --git a/Lib/fontTools/unicodedata/ScriptExtensions.py b/Lib/fontTools/unicodedata/ScriptExtensions.py
index d7d2e3c8..b4e09cd2 100644
--- a/Lib/fontTools/unicodedata/ScriptExtensions.py
+++ b/Lib/fontTools/unicodedata/ScriptExtensions.py
@@ -4,9 +4,9 @@
# Source: https://unicode.org/Public/UNIDATA/ScriptExtensions.txt
# License: http://unicode.org/copyright.html#License
#
-# ScriptExtensions-12.1.0.txt
-# Date: 2019-04-01, 09:10:42 GMT
-# © 2019 Unicode®, Inc.
+# ScriptExtensions-13.0.0.txt
+# Date: 2020-01-22, 00:07:43 GMT
+# © 2020 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
@@ -52,21 +52,19 @@ RANGES = [
0x0484, # .. 0x0484 ; {'Cyrl', 'Glag'}
0x0485, # .. 0x0486 ; {'Cyrl', 'Latn'}
0x0487, # .. 0x0487 ; {'Cyrl', 'Glag'}
- 0x0488, # .. 0x0588 ; None
- 0x0589, # .. 0x0589 ; {'Armn', 'Geor'}
- 0x058A, # .. 0x060B ; None
- 0x060C, # .. 0x060C ; {'Arab', 'Rohg', 'Syrc', 'Thaa'}
+ 0x0488, # .. 0x060B ; None
+ 0x060C, # .. 0x060C ; {'Arab', 'Rohg', 'Syrc', 'Thaa', 'Yezi'}
0x060D, # .. 0x061A ; None
- 0x061B, # .. 0x061B ; {'Arab', 'Rohg', 'Syrc', 'Thaa'}
+ 0x061B, # .. 0x061B ; {'Arab', 'Rohg', 'Syrc', 'Thaa', 'Yezi'}
0x061C, # .. 0x061C ; {'Arab', 'Syrc', 'Thaa'}
0x061D, # .. 0x061E ; None
- 0x061F, # .. 0x061F ; {'Arab', 'Rohg', 'Syrc', 'Thaa'}
+ 0x061F, # .. 0x061F ; {'Arab', 'Rohg', 'Syrc', 'Thaa', 'Yezi'}
0x0620, # .. 0x063F ; None
0x0640, # .. 0x0640 ; {'Adlm', 'Arab', 'Mand', 'Mani', 'Phlp', 'Rohg', 'Sogd', 'Syrc'}
0x0641, # .. 0x064A ; None
0x064B, # .. 0x0655 ; {'Arab', 'Syrc'}
0x0656, # .. 0x065F ; None
- 0x0660, # .. 0x0669 ; {'Arab', 'Thaa'}
+ 0x0660, # .. 0x0669 ; {'Arab', 'Thaa', 'Yezi'}
0x066A, # .. 0x066F ; None
0x0670, # .. 0x0670 ; {'Arab', 'Syrc'}
0x0671, # .. 0x06D3 ; None
@@ -129,7 +127,9 @@ RANGES = [
0x1CFA, # .. 0x1CFA ; {'Nand'}
0x1CFB, # .. 0x1DBF ; None
0x1DC0, # .. 0x1DC1 ; {'Grek'}
- 0x1DC2, # .. 0x202E ; None
+ 0x1DC2, # .. 0x1DF7 ; None
+ 0x1DF8, # .. 0x1DF8 ; {'Cyrl', 'Syrc'}
+ 0x1DF9, # .. 0x202E ; None
0x202F, # .. 0x202F ; {'Latn', 'Mong'}
0x2030, # .. 0x20EF ; None
0x20F0, # .. 0x20F0 ; {'Deva', 'Gran', 'Latn'}
@@ -183,7 +183,9 @@ RANGES = [
0x33E0, # .. 0x33FE ; {'Hani'}
0x33FF, # .. 0xA66E ; None
0xA66F, # .. 0xA66F ; {'Cyrl', 'Glag'}
- 0xA670, # .. 0xA82F ; None
+ 0xA670, # .. 0xA6FF ; None
+ 0xA700, # .. 0xA707 ; {'Hani', 'Latn'}
+ 0xA708, # .. 0xA82F ; None
0xA830, # .. 0xA832 ; {'Deva', 'Dogr', 'Gujr', 'Guru', 'Khoj', 'Knda', 'Kthi', 'Mahj', 'Mlym', 'Modi', 'Nand', 'Sind', 'Takr', 'Tirh'}
0xA833, # .. 0xA835 ; {'Deva', 'Dogr', 'Gujr', 'Guru', 'Khoj', 'Knda', 'Kthi', 'Mahj', 'Modi', 'Nand', 'Sind', 'Takr', 'Tirh'}
0xA836, # .. 0xA839 ; {'Deva', 'Dogr', 'Gujr', 'Guru', 'Khoj', 'Kthi', 'Mahj', 'Modi', 'Sind', 'Takr', 'Tirh'}
@@ -246,21 +248,19 @@ VALUES = [
{'Cyrl', 'Glag'}, # 0484..0484
{'Cyrl', 'Latn'}, # 0485..0486
{'Cyrl', 'Glag'}, # 0487..0487
- None, # 0488..0588
- {'Armn', 'Geor'}, # 0589..0589
- None, # 058A..060B
- {'Arab', 'Rohg', 'Syrc', 'Thaa'}, # 060C..060C
+ None, # 0488..060B
+ {'Arab', 'Rohg', 'Syrc', 'Thaa', 'Yezi'}, # 060C..060C
None, # 060D..061A
- {'Arab', 'Rohg', 'Syrc', 'Thaa'}, # 061B..061B
+ {'Arab', 'Rohg', 'Syrc', 'Thaa', 'Yezi'}, # 061B..061B
{'Arab', 'Syrc', 'Thaa'}, # 061C..061C
None, # 061D..061E
- {'Arab', 'Rohg', 'Syrc', 'Thaa'}, # 061F..061F
+ {'Arab', 'Rohg', 'Syrc', 'Thaa', 'Yezi'}, # 061F..061F
None, # 0620..063F
{'Adlm', 'Arab', 'Mand', 'Mani', 'Phlp', 'Rohg', 'Sogd', 'Syrc'}, # 0640..0640
None, # 0641..064A
{'Arab', 'Syrc'}, # 064B..0655
None, # 0656..065F
- {'Arab', 'Thaa'}, # 0660..0669
+ {'Arab', 'Thaa', 'Yezi'}, # 0660..0669
None, # 066A..066F
{'Arab', 'Syrc'}, # 0670..0670
None, # 0671..06D3
@@ -323,7 +323,9 @@ VALUES = [
{'Nand'}, # 1CFA..1CFA
None, # 1CFB..1DBF
{'Grek'}, # 1DC0..1DC1
- None, # 1DC2..202E
+ None, # 1DC2..1DF7
+ {'Cyrl', 'Syrc'}, # 1DF8..1DF8
+ None, # 1DF9..202E
{'Latn', 'Mong'}, # 202F..202F
None, # 2030..20EF
{'Deva', 'Gran', 'Latn'}, # 20F0..20F0
@@ -377,7 +379,9 @@ VALUES = [
{'Hani'}, # 33E0..33FE
None, # 33FF..A66E
{'Cyrl', 'Glag'}, # A66F..A66F
- None, # A670..A82F
+ None, # A670..A6FF
+ {'Hani', 'Latn'}, # A700..A707
+ None, # A708..A82F
{'Deva', 'Dogr', 'Gujr', 'Guru', 'Khoj', 'Knda', 'Kthi', 'Mahj', 'Mlym', 'Modi', 'Nand', 'Sind', 'Takr', 'Tirh'}, # A830..A832
{'Deva', 'Dogr', 'Gujr', 'Guru', 'Khoj', 'Knda', 'Kthi', 'Mahj', 'Modi', 'Nand', 'Sind', 'Takr', 'Tirh'}, # A833..A835
{'Deva', 'Dogr', 'Gujr', 'Guru', 'Khoj', 'Kthi', 'Mahj', 'Modi', 'Sind', 'Takr', 'Tirh'}, # A836..A839
diff --git a/Lib/fontTools/unicodedata/Scripts.py b/Lib/fontTools/unicodedata/Scripts.py
index dc8c1e2b..12f9a0e3 100644
--- a/Lib/fontTools/unicodedata/Scripts.py
+++ b/Lib/fontTools/unicodedata/Scripts.py
@@ -4,9 +4,9 @@
# Source: https://unicode.org/Public/UNIDATA/Scripts.txt
# License: http://unicode.org/copyright.html#License
#
-# Scripts-12.1.0.txt
-# Date: 2019-04-01, 09:10:42 GMT
-# © 2019 Unicode®, Inc.
+# Scripts-13.0.0.txt
+# Date: 2020-01-22, 00:07:43 GMT
+# © 2020 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
@@ -68,9 +68,7 @@ RANGES = [
0x0530, # .. 0x0530 ; Unknown
0x0531, # .. 0x0556 ; Armenian
0x0557, # .. 0x0558 ; Unknown
- 0x0559, # .. 0x0588 ; Armenian
- 0x0589, # .. 0x0589 ; Common
- 0x058A, # .. 0x058A ; Armenian
+ 0x0559, # .. 0x058A ; Armenian
0x058B, # .. 0x058C ; Unknown
0x058D, # .. 0x058F ; Armenian
0x0590, # .. 0x0590 ; Unknown
@@ -122,8 +120,8 @@ RANGES = [
0x086B, # .. 0x089F ; Unknown
0x08A0, # .. 0x08B4 ; Arabic
0x08B5, # .. 0x08B5 ; Unknown
- 0x08B6, # .. 0x08BD ; Arabic
- 0x08BE, # .. 0x08D2 ; Unknown
+ 0x08B6, # .. 0x08C7 ; Arabic
+ 0x08C8, # .. 0x08D2 ; Unknown
0x08D3, # .. 0x08E1 ; Arabic
0x08E2, # .. 0x08E2 ; Common
0x08E3, # .. 0x08FF ; Arabic
@@ -239,8 +237,8 @@ RANGES = [
0x0B47, # .. 0x0B48 ; Oriya
0x0B49, # .. 0x0B4A ; Unknown
0x0B4B, # .. 0x0B4D ; Oriya
- 0x0B4E, # .. 0x0B55 ; Unknown
- 0x0B56, # .. 0x0B57 ; Oriya
+ 0x0B4E, # .. 0x0B54 ; Unknown
+ 0x0B55, # .. 0x0B57 ; Oriya
0x0B58, # .. 0x0B5B ; Unknown
0x0B5C, # .. 0x0B5D ; Oriya
0x0B5E, # .. 0x0B5E ; Unknown
@@ -329,9 +327,7 @@ RANGES = [
0x0CF0, # .. 0x0CF0 ; Unknown
0x0CF1, # .. 0x0CF2 ; Kannada
0x0CF3, # .. 0x0CFF ; Unknown
- 0x0D00, # .. 0x0D03 ; Malayalam
- 0x0D04, # .. 0x0D04 ; Unknown
- 0x0D05, # .. 0x0D0C ; Malayalam
+ 0x0D00, # .. 0x0D0C ; Malayalam
0x0D0D, # .. 0x0D0D ; Unknown
0x0D0E, # .. 0x0D10 ; Malayalam
0x0D11, # .. 0x0D11 ; Unknown
@@ -344,8 +340,8 @@ RANGES = [
0x0D54, # .. 0x0D63 ; Malayalam
0x0D64, # .. 0x0D65 ; Unknown
0x0D66, # .. 0x0D7F ; Malayalam
- 0x0D80, # .. 0x0D81 ; Unknown
- 0x0D82, # .. 0x0D83 ; Sinhala
+ 0x0D80, # .. 0x0D80 ; Unknown
+ 0x0D81, # .. 0x0D83 ; Sinhala
0x0D84, # .. 0x0D84 ; Unknown
0x0D85, # .. 0x0D96 ; Sinhala
0x0D97, # .. 0x0D99 ; Unknown
@@ -537,8 +533,8 @@ RANGES = [
0x1A9A, # .. 0x1A9F ; Unknown
0x1AA0, # .. 0x1AAD ; Tai_Tham
0x1AAE, # .. 0x1AAF ; Unknown
- 0x1AB0, # .. 0x1ABE ; Inherited
- 0x1ABF, # .. 0x1AFF ; Unknown
+ 0x1AB0, # .. 0x1AC0 ; Inherited
+ 0x1AC1, # .. 0x1AFF ; Unknown
0x1B00, # .. 0x1B4B ; Balinese
0x1B4C, # .. 0x1B4F ; Unknown
0x1B50, # .. 0x1B7C ; Balinese
@@ -658,8 +654,8 @@ RANGES = [
0x2900, # .. 0x2B73 ; Common
0x2B74, # .. 0x2B75 ; Unknown
0x2B76, # .. 0x2B95 ; Common
- 0x2B96, # .. 0x2B97 ; Unknown
- 0x2B98, # .. 0x2BFF ; Common
+ 0x2B96, # .. 0x2B96 ; Unknown
+ 0x2B97, # .. 0x2BFF ; Common
0x2C00, # .. 0x2C2E ; Glagolitic
0x2C2F, # .. 0x2C2F ; Unknown
0x2C30, # .. 0x2C5E ; Glagolitic
@@ -698,8 +694,8 @@ RANGES = [
0x2DD8, # .. 0x2DDE ; Ethiopic
0x2DDF, # .. 0x2DDF ; Unknown
0x2DE0, # .. 0x2DFF ; Cyrillic
- 0x2E00, # .. 0x2E4F ; Common
- 0x2E50, # .. 0x2E7F ; Unknown
+ 0x2E00, # .. 0x2E52 ; Common
+ 0x2E53, # .. 0x2E7F ; Unknown
0x2E80, # .. 0x2E99 ; Han
0x2E9A, # .. 0x2E9A ; Unknown
0x2E9B, # .. 0x2EF3 ; Han
@@ -735,8 +731,7 @@ RANGES = [
0x3131, # .. 0x318E ; Hangul
0x318F, # .. 0x318F ; Unknown
0x3190, # .. 0x319F ; Common
- 0x31A0, # .. 0x31BA ; Bopomofo
- 0x31BB, # .. 0x31BF ; Unknown
+ 0x31A0, # .. 0x31BF ; Bopomofo
0x31C0, # .. 0x31E3 ; Common
0x31E4, # .. 0x31EF ; Unknown
0x31F0, # .. 0x31FF ; Katakana
@@ -749,11 +744,10 @@ RANGES = [
0x32FF, # .. 0x32FF ; Common
0x3300, # .. 0x3357 ; Katakana
0x3358, # .. 0x33FF ; Common
- 0x3400, # .. 0x4DB5 ; Han
- 0x4DB6, # .. 0x4DBF ; Unknown
+ 0x3400, # .. 0x4DBF ; Han
0x4DC0, # .. 0x4DFF ; Common
- 0x4E00, # .. 0x9FEF ; Han
- 0x9FF0, # .. 0x9FFF ; Unknown
+ 0x4E00, # .. 0x9FFC ; Han
+ 0x9FFD, # .. 0x9FFF ; Unknown
0xA000, # .. 0xA48C ; Yi
0xA48D, # .. 0xA48F ; Unknown
0xA490, # .. 0xA4C6 ; Yi
@@ -769,11 +763,11 @@ RANGES = [
0xA788, # .. 0xA78A ; Common
0xA78B, # .. 0xA7BF ; Latin
0xA7C0, # .. 0xA7C1 ; Unknown
- 0xA7C2, # .. 0xA7C6 ; Latin
- 0xA7C7, # .. 0xA7F6 ; Unknown
- 0xA7F7, # .. 0xA7FF ; Latin
- 0xA800, # .. 0xA82B ; Syloti_Nagri
- 0xA82C, # .. 0xA82F ; Unknown
+ 0xA7C2, # .. 0xA7CA ; Latin
+ 0xA7CB, # .. 0xA7F4 ; Unknown
+ 0xA7F5, # .. 0xA7FF ; Latin
+ 0xA800, # .. 0xA82C ; Syloti_Nagri
+ 0xA82D, # .. 0xA82F ; Unknown
0xA830, # .. 0xA839 ; Common
0xA83A, # .. 0xA83F ; Unknown
0xA840, # .. 0xA877 ; Phags_Pa
@@ -826,8 +820,9 @@ RANGES = [
0xAB5B, # .. 0xAB5B ; Common
0xAB5C, # .. 0xAB64 ; Latin
0xAB65, # .. 0xAB65 ; Greek
- 0xAB66, # .. 0xAB67 ; Latin
- 0xAB68, # .. 0xAB6F ; Unknown
+ 0xAB66, # .. 0xAB69 ; Latin
+ 0xAB6A, # .. 0xAB6B ; Common
+ 0xAB6C, # .. 0xAB6F ; Unknown
0xAB70, # .. 0xABBF ; Cherokee
0xABC0, # .. 0xABED ; Meetei_Mayek
0xABEE, # .. 0xABEF ; Unknown
@@ -932,8 +927,8 @@ RANGES = [
0x10137, # .. 0x1013F ; Common
0x10140, # .. 0x1018E ; Greek
0x1018F, # .. 0x1018F ; Unknown
- 0x10190, # .. 0x1019B ; Common
- 0x1019C, # .. 0x1019F ; Unknown
+ 0x10190, # .. 0x1019C ; Common
+ 0x1019D, # .. 0x1019F ; Unknown
0x101A0, # .. 0x101A0 ; Greek
0x101A1, # .. 0x101CF ; Unknown
0x101D0, # .. 0x101FC ; Common
@@ -1069,11 +1064,19 @@ RANGES = [
0x10D30, # .. 0x10D39 ; Hanifi_Rohingya
0x10D3A, # .. 0x10E5F ; Unknown
0x10E60, # .. 0x10E7E ; Arabic
- 0x10E7F, # .. 0x10EFF ; Unknown
+ 0x10E7F, # .. 0x10E7F ; Unknown
+ 0x10E80, # .. 0x10EA9 ; Yezidi
+ 0x10EAA, # .. 0x10EAA ; Unknown
+ 0x10EAB, # .. 0x10EAD ; Yezidi
+ 0x10EAE, # .. 0x10EAF ; Unknown
+ 0x10EB0, # .. 0x10EB1 ; Yezidi
+ 0x10EB2, # .. 0x10EFF ; Unknown
0x10F00, # .. 0x10F27 ; Old_Sogdian
0x10F28, # .. 0x10F2F ; Unknown
0x10F30, # .. 0x10F59 ; Sogdian
- 0x10F5A, # .. 0x10FDF ; Unknown
+ 0x10F5A, # .. 0x10FAF ; Unknown
+ 0x10FB0, # .. 0x10FCB ; Chorasmian
+ 0x10FCC, # .. 0x10FDF ; Unknown
0x10FE0, # .. 0x10FF6 ; Elymaic
0x10FF7, # .. 0x10FFF ; Unknown
0x11000, # .. 0x1104D ; Brahmi
@@ -1091,13 +1094,11 @@ RANGES = [
0x110FA, # .. 0x110FF ; Unknown
0x11100, # .. 0x11134 ; Chakma
0x11135, # .. 0x11135 ; Unknown
- 0x11136, # .. 0x11146 ; Chakma
- 0x11147, # .. 0x1114F ; Unknown
+ 0x11136, # .. 0x11147 ; Chakma
+ 0x11148, # .. 0x1114F ; Unknown
0x11150, # .. 0x11176 ; Mahajani
0x11177, # .. 0x1117F ; Unknown
- 0x11180, # .. 0x111CD ; Sharada
- 0x111CE, # .. 0x111CF ; Unknown
- 0x111D0, # .. 0x111DF ; Sharada
+ 0x11180, # .. 0x111DF ; Sharada
0x111E0, # .. 0x111E0 ; Unknown
0x111E1, # .. 0x111F4 ; Sinhala
0x111F5, # .. 0x111FF ; Unknown
@@ -1150,12 +1151,10 @@ RANGES = [
0x1136D, # .. 0x1136F ; Unknown
0x11370, # .. 0x11374 ; Grantha
0x11375, # .. 0x113FF ; Unknown
- 0x11400, # .. 0x11459 ; Newa
- 0x1145A, # .. 0x1145A ; Unknown
- 0x1145B, # .. 0x1145B ; Newa
+ 0x11400, # .. 0x1145B ; Newa
0x1145C, # .. 0x1145C ; Unknown
- 0x1145D, # .. 0x1145F ; Newa
- 0x11460, # .. 0x1147F ; Unknown
+ 0x1145D, # .. 0x11461 ; Newa
+ 0x11462, # .. 0x1147F ; Unknown
0x11480, # .. 0x114C7 ; Tirhuta
0x114C8, # .. 0x114CF ; Unknown
0x114D0, # .. 0x114D9 ; Tirhuta
@@ -1185,7 +1184,22 @@ RANGES = [
0x118A0, # .. 0x118F2 ; Warang_Citi
0x118F3, # .. 0x118FE ; Unknown
0x118FF, # .. 0x118FF ; Warang_Citi
- 0x11900, # .. 0x1199F ; Unknown
+ 0x11900, # .. 0x11906 ; Dives_Akuru
+ 0x11907, # .. 0x11908 ; Unknown
+ 0x11909, # .. 0x11909 ; Dives_Akuru
+ 0x1190A, # .. 0x1190B ; Unknown
+ 0x1190C, # .. 0x11913 ; Dives_Akuru
+ 0x11914, # .. 0x11914 ; Unknown
+ 0x11915, # .. 0x11916 ; Dives_Akuru
+ 0x11917, # .. 0x11917 ; Unknown
+ 0x11918, # .. 0x11935 ; Dives_Akuru
+ 0x11936, # .. 0x11936 ; Unknown
+ 0x11937, # .. 0x11938 ; Dives_Akuru
+ 0x11939, # .. 0x1193A ; Unknown
+ 0x1193B, # .. 0x11946 ; Dives_Akuru
+ 0x11947, # .. 0x1194F ; Unknown
+ 0x11950, # .. 0x11959 ; Dives_Akuru
+ 0x1195A, # .. 0x1199F ; Unknown
0x119A0, # .. 0x119A7 ; Nandinagari
0x119A8, # .. 0x119A9 ; Unknown
0x119AA, # .. 0x119D7 ; Nandinagari
@@ -1239,7 +1253,9 @@ RANGES = [
0x11DA0, # .. 0x11DA9 ; Gunjala_Gondi
0x11DAA, # .. 0x11EDF ; Unknown
0x11EE0, # .. 0x11EF8 ; Makasar
- 0x11EF9, # .. 0x11FBF ; Unknown
+ 0x11EF9, # .. 0x11FAF ; Unknown
+ 0x11FB0, # .. 0x11FB0 ; Lisu
+ 0x11FB1, # .. 0x11FBF ; Unknown
0x11FC0, # .. 0x11FF1 ; Tamil
0x11FF2, # .. 0x11FFE ; Unknown
0x11FFF, # .. 0x11FFF ; Tamil
@@ -1290,11 +1306,17 @@ RANGES = [
0x16FE0, # .. 0x16FE0 ; Tangut
0x16FE1, # .. 0x16FE1 ; Nushu
0x16FE2, # .. 0x16FE3 ; Common
- 0x16FE4, # .. 0x16FFF ; Unknown
+ 0x16FE4, # .. 0x16FE4 ; Khitan_Small_Script
+ 0x16FE5, # .. 0x16FEF ; Unknown
+ 0x16FF0, # .. 0x16FF1 ; Han
+ 0x16FF2, # .. 0x16FFF ; Unknown
0x17000, # .. 0x187F7 ; Tangut
0x187F8, # .. 0x187FF ; Unknown
- 0x18800, # .. 0x18AF2 ; Tangut
- 0x18AF3, # .. 0x1AFFF ; Unknown
+ 0x18800, # .. 0x18AFF ; Tangut
+ 0x18B00, # .. 0x18CD5 ; Khitan_Small_Script
+ 0x18CD6, # .. 0x18CFF ; Unknown
+ 0x18D00, # .. 0x18D08 ; Tangut
+ 0x18D09, # .. 0x1AFFF ; Unknown
0x1B000, # .. 0x1B000 ; Katakana
0x1B001, # .. 0x1B11E ; Hiragana
0x1B11F, # .. 0x1B14F ; Unknown
@@ -1500,12 +1522,8 @@ RANGES = [
0x1F0D0, # .. 0x1F0D0 ; Unknown
0x1F0D1, # .. 0x1F0F5 ; Common
0x1F0F6, # .. 0x1F0FF ; Unknown
- 0x1F100, # .. 0x1F10C ; Common
- 0x1F10D, # .. 0x1F10F ; Unknown
- 0x1F110, # .. 0x1F16C ; Common
- 0x1F16D, # .. 0x1F16F ; Unknown
- 0x1F170, # .. 0x1F1AC ; Common
- 0x1F1AD, # .. 0x1F1E5 ; Unknown
+ 0x1F100, # .. 0x1F1AD ; Common
+ 0x1F1AE, # .. 0x1F1E5 ; Unknown
0x1F1E6, # .. 0x1F1FF ; Common
0x1F200, # .. 0x1F200 ; Hiragana
0x1F201, # .. 0x1F202 ; Common
@@ -1518,12 +1536,12 @@ RANGES = [
0x1F252, # .. 0x1F25F ; Unknown
0x1F260, # .. 0x1F265 ; Common
0x1F266, # .. 0x1F2FF ; Unknown
- 0x1F300, # .. 0x1F6D5 ; Common
- 0x1F6D6, # .. 0x1F6DF ; Unknown
+ 0x1F300, # .. 0x1F6D7 ; Common
+ 0x1F6D8, # .. 0x1F6DF ; Unknown
0x1F6E0, # .. 0x1F6EC ; Common
0x1F6ED, # .. 0x1F6EF ; Unknown
- 0x1F6F0, # .. 0x1F6FA ; Common
- 0x1F6FB, # .. 0x1F6FF ; Unknown
+ 0x1F6F0, # .. 0x1F6FC ; Common
+ 0x1F6FD, # .. 0x1F6FF ; Unknown
0x1F700, # .. 0x1F773 ; Common
0x1F774, # .. 0x1F77F ; Unknown
0x1F780, # .. 0x1F7D8 ; Common
@@ -1539,33 +1557,39 @@ RANGES = [
0x1F860, # .. 0x1F887 ; Common
0x1F888, # .. 0x1F88F ; Unknown
0x1F890, # .. 0x1F8AD ; Common
- 0x1F8AE, # .. 0x1F8FF ; Unknown
- 0x1F900, # .. 0x1F90B ; Common
- 0x1F90C, # .. 0x1F90C ; Unknown
- 0x1F90D, # .. 0x1F971 ; Common
- 0x1F972, # .. 0x1F972 ; Unknown
- 0x1F973, # .. 0x1F976 ; Common
- 0x1F977, # .. 0x1F979 ; Unknown
- 0x1F97A, # .. 0x1F9A2 ; Common
- 0x1F9A3, # .. 0x1F9A4 ; Unknown
- 0x1F9A5, # .. 0x1F9AA ; Common
- 0x1F9AB, # .. 0x1F9AD ; Unknown
- 0x1F9AE, # .. 0x1F9CA ; Common
- 0x1F9CB, # .. 0x1F9CC ; Unknown
+ 0x1F8AE, # .. 0x1F8AF ; Unknown
+ 0x1F8B0, # .. 0x1F8B1 ; Common
+ 0x1F8B2, # .. 0x1F8FF ; Unknown
+ 0x1F900, # .. 0x1F978 ; Common
+ 0x1F979, # .. 0x1F979 ; Unknown
+ 0x1F97A, # .. 0x1F9CB ; Common
+ 0x1F9CC, # .. 0x1F9CC ; Unknown
0x1F9CD, # .. 0x1FA53 ; Common
0x1FA54, # .. 0x1FA5F ; Unknown
0x1FA60, # .. 0x1FA6D ; Common
0x1FA6E, # .. 0x1FA6F ; Unknown
- 0x1FA70, # .. 0x1FA73 ; Common
- 0x1FA74, # .. 0x1FA77 ; Unknown
+ 0x1FA70, # .. 0x1FA74 ; Common
+ 0x1FA75, # .. 0x1FA77 ; Unknown
0x1FA78, # .. 0x1FA7A ; Common
0x1FA7B, # .. 0x1FA7F ; Unknown
- 0x1FA80, # .. 0x1FA82 ; Common
- 0x1FA83, # .. 0x1FA8F ; Unknown
- 0x1FA90, # .. 0x1FA95 ; Common
- 0x1FA96, # .. 0x1FFFF ; Unknown
- 0x20000, # .. 0x2A6D6 ; Han
- 0x2A6D7, # .. 0x2A6FF ; Unknown
+ 0x1FA80, # .. 0x1FA86 ; Common
+ 0x1FA87, # .. 0x1FA8F ; Unknown
+ 0x1FA90, # .. 0x1FAA8 ; Common
+ 0x1FAA9, # .. 0x1FAAF ; Unknown
+ 0x1FAB0, # .. 0x1FAB6 ; Common
+ 0x1FAB7, # .. 0x1FABF ; Unknown
+ 0x1FAC0, # .. 0x1FAC2 ; Common
+ 0x1FAC3, # .. 0x1FACF ; Unknown
+ 0x1FAD0, # .. 0x1FAD6 ; Common
+ 0x1FAD7, # .. 0x1FAFF ; Unknown
+ 0x1FB00, # .. 0x1FB92 ; Common
+ 0x1FB93, # .. 0x1FB93 ; Unknown
+ 0x1FB94, # .. 0x1FBCA ; Common
+ 0x1FBCB, # .. 0x1FBEF ; Unknown
+ 0x1FBF0, # .. 0x1FBF9 ; Common
+ 0x1FBFA, # .. 0x1FFFF ; Unknown
+ 0x20000, # .. 0x2A6DD ; Han
+ 0x2A6DE, # .. 0x2A6FF ; Unknown
0x2A700, # .. 0x2B734 ; Han
0x2B735, # .. 0x2B73F ; Unknown
0x2B740, # .. 0x2B81D ; Han
@@ -1575,7 +1599,9 @@ RANGES = [
0x2CEB0, # .. 0x2EBE0 ; Han
0x2EBE1, # .. 0x2F7FF ; Unknown
0x2F800, # .. 0x2FA1D ; Han
- 0x2FA1E, # .. 0xE0000 ; Unknown
+ 0x2FA1E, # .. 0x2FFFF ; Unknown
+ 0x30000, # .. 0x3134A ; Han
+ 0x3134B, # .. 0xE0000 ; Unknown
0xE0001, # .. 0xE0001 ; Common
0xE0002, # .. 0xE001F ; Unknown
0xE0020, # .. 0xE007F ; Common
@@ -1632,9 +1658,7 @@ VALUES = [
'Zzzz', # 0530..0530 ; Unknown
'Armn', # 0531..0556 ; Armenian
'Zzzz', # 0557..0558 ; Unknown
- 'Armn', # 0559..0588 ; Armenian
- 'Zyyy', # 0589..0589 ; Common
- 'Armn', # 058A..058A ; Armenian
+ 'Armn', # 0559..058A ; Armenian
'Zzzz', # 058B..058C ; Unknown
'Armn', # 058D..058F ; Armenian
'Zzzz', # 0590..0590 ; Unknown
@@ -1686,8 +1710,8 @@ VALUES = [
'Zzzz', # 086B..089F ; Unknown
'Arab', # 08A0..08B4 ; Arabic
'Zzzz', # 08B5..08B5 ; Unknown
- 'Arab', # 08B6..08BD ; Arabic
- 'Zzzz', # 08BE..08D2 ; Unknown
+ 'Arab', # 08B6..08C7 ; Arabic
+ 'Zzzz', # 08C8..08D2 ; Unknown
'Arab', # 08D3..08E1 ; Arabic
'Zyyy', # 08E2..08E2 ; Common
'Arab', # 08E3..08FF ; Arabic
@@ -1803,8 +1827,8 @@ VALUES = [
'Orya', # 0B47..0B48 ; Oriya
'Zzzz', # 0B49..0B4A ; Unknown
'Orya', # 0B4B..0B4D ; Oriya
- 'Zzzz', # 0B4E..0B55 ; Unknown
- 'Orya', # 0B56..0B57 ; Oriya
+ 'Zzzz', # 0B4E..0B54 ; Unknown
+ 'Orya', # 0B55..0B57 ; Oriya
'Zzzz', # 0B58..0B5B ; Unknown
'Orya', # 0B5C..0B5D ; Oriya
'Zzzz', # 0B5E..0B5E ; Unknown
@@ -1893,9 +1917,7 @@ VALUES = [
'Zzzz', # 0CF0..0CF0 ; Unknown
'Knda', # 0CF1..0CF2 ; Kannada
'Zzzz', # 0CF3..0CFF ; Unknown
- 'Mlym', # 0D00..0D03 ; Malayalam
- 'Zzzz', # 0D04..0D04 ; Unknown
- 'Mlym', # 0D05..0D0C ; Malayalam
+ 'Mlym', # 0D00..0D0C ; Malayalam
'Zzzz', # 0D0D..0D0D ; Unknown
'Mlym', # 0D0E..0D10 ; Malayalam
'Zzzz', # 0D11..0D11 ; Unknown
@@ -1908,8 +1930,8 @@ VALUES = [
'Mlym', # 0D54..0D63 ; Malayalam
'Zzzz', # 0D64..0D65 ; Unknown
'Mlym', # 0D66..0D7F ; Malayalam
- 'Zzzz', # 0D80..0D81 ; Unknown
- 'Sinh', # 0D82..0D83 ; Sinhala
+ 'Zzzz', # 0D80..0D80 ; Unknown
+ 'Sinh', # 0D81..0D83 ; Sinhala
'Zzzz', # 0D84..0D84 ; Unknown
'Sinh', # 0D85..0D96 ; Sinhala
'Zzzz', # 0D97..0D99 ; Unknown
@@ -2101,8 +2123,8 @@ VALUES = [
'Zzzz', # 1A9A..1A9F ; Unknown
'Lana', # 1AA0..1AAD ; Tai_Tham
'Zzzz', # 1AAE..1AAF ; Unknown
- 'Zinh', # 1AB0..1ABE ; Inherited
- 'Zzzz', # 1ABF..1AFF ; Unknown
+ 'Zinh', # 1AB0..1AC0 ; Inherited
+ 'Zzzz', # 1AC1..1AFF ; Unknown
'Bali', # 1B00..1B4B ; Balinese
'Zzzz', # 1B4C..1B4F ; Unknown
'Bali', # 1B50..1B7C ; Balinese
@@ -2222,8 +2244,8 @@ VALUES = [
'Zyyy', # 2900..2B73 ; Common
'Zzzz', # 2B74..2B75 ; Unknown
'Zyyy', # 2B76..2B95 ; Common
- 'Zzzz', # 2B96..2B97 ; Unknown
- 'Zyyy', # 2B98..2BFF ; Common
+ 'Zzzz', # 2B96..2B96 ; Unknown
+ 'Zyyy', # 2B97..2BFF ; Common
'Glag', # 2C00..2C2E ; Glagolitic
'Zzzz', # 2C2F..2C2F ; Unknown
'Glag', # 2C30..2C5E ; Glagolitic
@@ -2262,8 +2284,8 @@ VALUES = [
'Ethi', # 2DD8..2DDE ; Ethiopic
'Zzzz', # 2DDF..2DDF ; Unknown
'Cyrl', # 2DE0..2DFF ; Cyrillic
- 'Zyyy', # 2E00..2E4F ; Common
- 'Zzzz', # 2E50..2E7F ; Unknown
+ 'Zyyy', # 2E00..2E52 ; Common
+ 'Zzzz', # 2E53..2E7F ; Unknown
'Hani', # 2E80..2E99 ; Han
'Zzzz', # 2E9A..2E9A ; Unknown
'Hani', # 2E9B..2EF3 ; Han
@@ -2299,8 +2321,7 @@ VALUES = [
'Hang', # 3131..318E ; Hangul
'Zzzz', # 318F..318F ; Unknown
'Zyyy', # 3190..319F ; Common
- 'Bopo', # 31A0..31BA ; Bopomofo
- 'Zzzz', # 31BB..31BF ; Unknown
+ 'Bopo', # 31A0..31BF ; Bopomofo
'Zyyy', # 31C0..31E3 ; Common
'Zzzz', # 31E4..31EF ; Unknown
'Kana', # 31F0..31FF ; Katakana
@@ -2313,11 +2334,10 @@ VALUES = [
'Zyyy', # 32FF..32FF ; Common
'Kana', # 3300..3357 ; Katakana
'Zyyy', # 3358..33FF ; Common
- 'Hani', # 3400..4DB5 ; Han
- 'Zzzz', # 4DB6..4DBF ; Unknown
+ 'Hani', # 3400..4DBF ; Han
'Zyyy', # 4DC0..4DFF ; Common
- 'Hani', # 4E00..9FEF ; Han
- 'Zzzz', # 9FF0..9FFF ; Unknown
+ 'Hani', # 4E00..9FFC ; Han
+ 'Zzzz', # 9FFD..9FFF ; Unknown
'Yiii', # A000..A48C ; Yi
'Zzzz', # A48D..A48F ; Unknown
'Yiii', # A490..A4C6 ; Yi
@@ -2333,11 +2353,11 @@ VALUES = [
'Zyyy', # A788..A78A ; Common
'Latn', # A78B..A7BF ; Latin
'Zzzz', # A7C0..A7C1 ; Unknown
- 'Latn', # A7C2..A7C6 ; Latin
- 'Zzzz', # A7C7..A7F6 ; Unknown
- 'Latn', # A7F7..A7FF ; Latin
- 'Sylo', # A800..A82B ; Syloti_Nagri
- 'Zzzz', # A82C..A82F ; Unknown
+ 'Latn', # A7C2..A7CA ; Latin
+ 'Zzzz', # A7CB..A7F4 ; Unknown
+ 'Latn', # A7F5..A7FF ; Latin
+ 'Sylo', # A800..A82C ; Syloti_Nagri
+ 'Zzzz', # A82D..A82F ; Unknown
'Zyyy', # A830..A839 ; Common
'Zzzz', # A83A..A83F ; Unknown
'Phag', # A840..A877 ; Phags_Pa
@@ -2390,8 +2410,9 @@ VALUES = [
'Zyyy', # AB5B..AB5B ; Common
'Latn', # AB5C..AB64 ; Latin
'Grek', # AB65..AB65 ; Greek
- 'Latn', # AB66..AB67 ; Latin
- 'Zzzz', # AB68..AB6F ; Unknown
+ 'Latn', # AB66..AB69 ; Latin
+ 'Zyyy', # AB6A..AB6B ; Common
+ 'Zzzz', # AB6C..AB6F ; Unknown
'Cher', # AB70..ABBF ; Cherokee
'Mtei', # ABC0..ABED ; Meetei_Mayek
'Zzzz', # ABEE..ABEF ; Unknown
@@ -2496,8 +2517,8 @@ VALUES = [
'Zyyy', # 10137..1013F ; Common
'Grek', # 10140..1018E ; Greek
'Zzzz', # 1018F..1018F ; Unknown
- 'Zyyy', # 10190..1019B ; Common
- 'Zzzz', # 1019C..1019F ; Unknown
+ 'Zyyy', # 10190..1019C ; Common
+ 'Zzzz', # 1019D..1019F ; Unknown
'Grek', # 101A0..101A0 ; Greek
'Zzzz', # 101A1..101CF ; Unknown
'Zyyy', # 101D0..101FC ; Common
@@ -2633,11 +2654,19 @@ VALUES = [
'Rohg', # 10D30..10D39 ; Hanifi_Rohingya
'Zzzz', # 10D3A..10E5F ; Unknown
'Arab', # 10E60..10E7E ; Arabic
- 'Zzzz', # 10E7F..10EFF ; Unknown
+ 'Zzzz', # 10E7F..10E7F ; Unknown
+ 'Yezi', # 10E80..10EA9 ; Yezidi
+ 'Zzzz', # 10EAA..10EAA ; Unknown
+ 'Yezi', # 10EAB..10EAD ; Yezidi
+ 'Zzzz', # 10EAE..10EAF ; Unknown
+ 'Yezi', # 10EB0..10EB1 ; Yezidi
+ 'Zzzz', # 10EB2..10EFF ; Unknown
'Sogo', # 10F00..10F27 ; Old_Sogdian
'Zzzz', # 10F28..10F2F ; Unknown
'Sogd', # 10F30..10F59 ; Sogdian
- 'Zzzz', # 10F5A..10FDF ; Unknown
+ 'Zzzz', # 10F5A..10FAF ; Unknown
+ 'Chrs', # 10FB0..10FCB ; Chorasmian
+ 'Zzzz', # 10FCC..10FDF ; Unknown
'Elym', # 10FE0..10FF6 ; Elymaic
'Zzzz', # 10FF7..10FFF ; Unknown
'Brah', # 11000..1104D ; Brahmi
@@ -2655,13 +2684,11 @@ VALUES = [
'Zzzz', # 110FA..110FF ; Unknown
'Cakm', # 11100..11134 ; Chakma
'Zzzz', # 11135..11135 ; Unknown
- 'Cakm', # 11136..11146 ; Chakma
- 'Zzzz', # 11147..1114F ; Unknown
+ 'Cakm', # 11136..11147 ; Chakma
+ 'Zzzz', # 11148..1114F ; Unknown
'Mahj', # 11150..11176 ; Mahajani
'Zzzz', # 11177..1117F ; Unknown
- 'Shrd', # 11180..111CD ; Sharada
- 'Zzzz', # 111CE..111CF ; Unknown
- 'Shrd', # 111D0..111DF ; Sharada
+ 'Shrd', # 11180..111DF ; Sharada
'Zzzz', # 111E0..111E0 ; Unknown
'Sinh', # 111E1..111F4 ; Sinhala
'Zzzz', # 111F5..111FF ; Unknown
@@ -2714,12 +2741,10 @@ VALUES = [
'Zzzz', # 1136D..1136F ; Unknown
'Gran', # 11370..11374 ; Grantha
'Zzzz', # 11375..113FF ; Unknown
- 'Newa', # 11400..11459 ; Newa
- 'Zzzz', # 1145A..1145A ; Unknown
- 'Newa', # 1145B..1145B ; Newa
+ 'Newa', # 11400..1145B ; Newa
'Zzzz', # 1145C..1145C ; Unknown
- 'Newa', # 1145D..1145F ; Newa
- 'Zzzz', # 11460..1147F ; Unknown
+ 'Newa', # 1145D..11461 ; Newa
+ 'Zzzz', # 11462..1147F ; Unknown
'Tirh', # 11480..114C7 ; Tirhuta
'Zzzz', # 114C8..114CF ; Unknown
'Tirh', # 114D0..114D9 ; Tirhuta
@@ -2749,7 +2774,22 @@ VALUES = [
'Wara', # 118A0..118F2 ; Warang_Citi
'Zzzz', # 118F3..118FE ; Unknown
'Wara', # 118FF..118FF ; Warang_Citi
- 'Zzzz', # 11900..1199F ; Unknown
+ 'Diak', # 11900..11906 ; Dives_Akuru
+ 'Zzzz', # 11907..11908 ; Unknown
+ 'Diak', # 11909..11909 ; Dives_Akuru
+ 'Zzzz', # 1190A..1190B ; Unknown
+ 'Diak', # 1190C..11913 ; Dives_Akuru
+ 'Zzzz', # 11914..11914 ; Unknown
+ 'Diak', # 11915..11916 ; Dives_Akuru
+ 'Zzzz', # 11917..11917 ; Unknown
+ 'Diak', # 11918..11935 ; Dives_Akuru
+ 'Zzzz', # 11936..11936 ; Unknown
+ 'Diak', # 11937..11938 ; Dives_Akuru
+ 'Zzzz', # 11939..1193A ; Unknown
+ 'Diak', # 1193B..11946 ; Dives_Akuru
+ 'Zzzz', # 11947..1194F ; Unknown
+ 'Diak', # 11950..11959 ; Dives_Akuru
+ 'Zzzz', # 1195A..1199F ; Unknown
'Nand', # 119A0..119A7 ; Nandinagari
'Zzzz', # 119A8..119A9 ; Unknown
'Nand', # 119AA..119D7 ; Nandinagari
@@ -2803,7 +2843,9 @@ VALUES = [
'Gong', # 11DA0..11DA9 ; Gunjala_Gondi
'Zzzz', # 11DAA..11EDF ; Unknown
'Maka', # 11EE0..11EF8 ; Makasar
- 'Zzzz', # 11EF9..11FBF ; Unknown
+ 'Zzzz', # 11EF9..11FAF ; Unknown
+ 'Lisu', # 11FB0..11FB0 ; Lisu
+ 'Zzzz', # 11FB1..11FBF ; Unknown
'Taml', # 11FC0..11FF1 ; Tamil
'Zzzz', # 11FF2..11FFE ; Unknown
'Taml', # 11FFF..11FFF ; Tamil
@@ -2854,11 +2896,17 @@ VALUES = [
'Tang', # 16FE0..16FE0 ; Tangut
'Nshu', # 16FE1..16FE1 ; Nushu
'Zyyy', # 16FE2..16FE3 ; Common
- 'Zzzz', # 16FE4..16FFF ; Unknown
+ 'Kits', # 16FE4..16FE4 ; Khitan_Small_Script
+ 'Zzzz', # 16FE5..16FEF ; Unknown
+ 'Hani', # 16FF0..16FF1 ; Han
+ 'Zzzz', # 16FF2..16FFF ; Unknown
'Tang', # 17000..187F7 ; Tangut
'Zzzz', # 187F8..187FF ; Unknown
- 'Tang', # 18800..18AF2 ; Tangut
- 'Zzzz', # 18AF3..1AFFF ; Unknown
+ 'Tang', # 18800..18AFF ; Tangut
+ 'Kits', # 18B00..18CD5 ; Khitan_Small_Script
+ 'Zzzz', # 18CD6..18CFF ; Unknown
+ 'Tang', # 18D00..18D08 ; Tangut
+ 'Zzzz', # 18D09..1AFFF ; Unknown
'Kana', # 1B000..1B000 ; Katakana
'Hira', # 1B001..1B11E ; Hiragana
'Zzzz', # 1B11F..1B14F ; Unknown
@@ -3064,12 +3112,8 @@ VALUES = [
'Zzzz', # 1F0D0..1F0D0 ; Unknown
'Zyyy', # 1F0D1..1F0F5 ; Common
'Zzzz', # 1F0F6..1F0FF ; Unknown
- 'Zyyy', # 1F100..1F10C ; Common
- 'Zzzz', # 1F10D..1F10F ; Unknown
- 'Zyyy', # 1F110..1F16C ; Common
- 'Zzzz', # 1F16D..1F16F ; Unknown
- 'Zyyy', # 1F170..1F1AC ; Common
- 'Zzzz', # 1F1AD..1F1E5 ; Unknown
+ 'Zyyy', # 1F100..1F1AD ; Common
+ 'Zzzz', # 1F1AE..1F1E5 ; Unknown
'Zyyy', # 1F1E6..1F1FF ; Common
'Hira', # 1F200..1F200 ; Hiragana
'Zyyy', # 1F201..1F202 ; Common
@@ -3082,12 +3126,12 @@ VALUES = [
'Zzzz', # 1F252..1F25F ; Unknown
'Zyyy', # 1F260..1F265 ; Common
'Zzzz', # 1F266..1F2FF ; Unknown
- 'Zyyy', # 1F300..1F6D5 ; Common
- 'Zzzz', # 1F6D6..1F6DF ; Unknown
+ 'Zyyy', # 1F300..1F6D7 ; Common
+ 'Zzzz', # 1F6D8..1F6DF ; Unknown
'Zyyy', # 1F6E0..1F6EC ; Common
'Zzzz', # 1F6ED..1F6EF ; Unknown
- 'Zyyy', # 1F6F0..1F6FA ; Common
- 'Zzzz', # 1F6FB..1F6FF ; Unknown
+ 'Zyyy', # 1F6F0..1F6FC ; Common
+ 'Zzzz', # 1F6FD..1F6FF ; Unknown
'Zyyy', # 1F700..1F773 ; Common
'Zzzz', # 1F774..1F77F ; Unknown
'Zyyy', # 1F780..1F7D8 ; Common
@@ -3103,33 +3147,39 @@ VALUES = [
'Zyyy', # 1F860..1F887 ; Common
'Zzzz', # 1F888..1F88F ; Unknown
'Zyyy', # 1F890..1F8AD ; Common
- 'Zzzz', # 1F8AE..1F8FF ; Unknown
- 'Zyyy', # 1F900..1F90B ; Common
- 'Zzzz', # 1F90C..1F90C ; Unknown
- 'Zyyy', # 1F90D..1F971 ; Common
- 'Zzzz', # 1F972..1F972 ; Unknown
- 'Zyyy', # 1F973..1F976 ; Common
- 'Zzzz', # 1F977..1F979 ; Unknown
- 'Zyyy', # 1F97A..1F9A2 ; Common
- 'Zzzz', # 1F9A3..1F9A4 ; Unknown
- 'Zyyy', # 1F9A5..1F9AA ; Common
- 'Zzzz', # 1F9AB..1F9AD ; Unknown
- 'Zyyy', # 1F9AE..1F9CA ; Common
- 'Zzzz', # 1F9CB..1F9CC ; Unknown
+ 'Zzzz', # 1F8AE..1F8AF ; Unknown
+ 'Zyyy', # 1F8B0..1F8B1 ; Common
+ 'Zzzz', # 1F8B2..1F8FF ; Unknown
+ 'Zyyy', # 1F900..1F978 ; Common
+ 'Zzzz', # 1F979..1F979 ; Unknown
+ 'Zyyy', # 1F97A..1F9CB ; Common
+ 'Zzzz', # 1F9CC..1F9CC ; Unknown
'Zyyy', # 1F9CD..1FA53 ; Common
'Zzzz', # 1FA54..1FA5F ; Unknown
'Zyyy', # 1FA60..1FA6D ; Common
'Zzzz', # 1FA6E..1FA6F ; Unknown
- 'Zyyy', # 1FA70..1FA73 ; Common
- 'Zzzz', # 1FA74..1FA77 ; Unknown
+ 'Zyyy', # 1FA70..1FA74 ; Common
+ 'Zzzz', # 1FA75..1FA77 ; Unknown
'Zyyy', # 1FA78..1FA7A ; Common
'Zzzz', # 1FA7B..1FA7F ; Unknown
- 'Zyyy', # 1FA80..1FA82 ; Common
- 'Zzzz', # 1FA83..1FA8F ; Unknown
- 'Zyyy', # 1FA90..1FA95 ; Common
- 'Zzzz', # 1FA96..1FFFF ; Unknown
- 'Hani', # 20000..2A6D6 ; Han
- 'Zzzz', # 2A6D7..2A6FF ; Unknown
+ 'Zyyy', # 1FA80..1FA86 ; Common
+ 'Zzzz', # 1FA87..1FA8F ; Unknown
+ 'Zyyy', # 1FA90..1FAA8 ; Common
+ 'Zzzz', # 1FAA9..1FAAF ; Unknown
+ 'Zyyy', # 1FAB0..1FAB6 ; Common
+ 'Zzzz', # 1FAB7..1FABF ; Unknown
+ 'Zyyy', # 1FAC0..1FAC2 ; Common
+ 'Zzzz', # 1FAC3..1FACF ; Unknown
+ 'Zyyy', # 1FAD0..1FAD6 ; Common
+ 'Zzzz', # 1FAD7..1FAFF ; Unknown
+ 'Zyyy', # 1FB00..1FB92 ; Common
+ 'Zzzz', # 1FB93..1FB93 ; Unknown
+ 'Zyyy', # 1FB94..1FBCA ; Common
+ 'Zzzz', # 1FBCB..1FBEF ; Unknown
+ 'Zyyy', # 1FBF0..1FBF9 ; Common
+ 'Zzzz', # 1FBFA..1FFFF ; Unknown
+ 'Hani', # 20000..2A6DD ; Han
+ 'Zzzz', # 2A6DE..2A6FF ; Unknown
'Hani', # 2A700..2B734 ; Han
'Zzzz', # 2B735..2B73F ; Unknown
'Hani', # 2B740..2B81D ; Han
@@ -3139,7 +3189,9 @@ VALUES = [
'Hani', # 2CEB0..2EBE0 ; Han
'Zzzz', # 2EBE1..2F7FF ; Unknown
'Hani', # 2F800..2FA1D ; Han
- 'Zzzz', # 2FA1E..E0000 ; Unknown
+ 'Zzzz', # 2FA1E..2FFFF ; Unknown
+ 'Hani', # 30000..3134A ; Han
+ 'Zzzz', # 3134B..E0000 ; Unknown
'Zyyy', # E0001..E0001 ; Common
'Zzzz', # E0002..E001F ; Unknown
'Zyyy', # E0020..E007F ; Common
@@ -3172,10 +3224,12 @@ NAMES = {
'Cari': 'Carian',
'Cham': 'Cham',
'Cher': 'Cherokee',
+ 'Chrs': 'Chorasmian',
'Copt': 'Coptic',
'Cprt': 'Cypriot',
'Cyrl': 'Cyrillic',
'Deva': 'Devanagari',
+ 'Diak': 'Dives_Akuru',
'Dogr': 'Dogra',
'Dsrt': 'Deseret',
'Dupl': 'Duployan',
@@ -3210,6 +3264,7 @@ NAMES = {
'Khar': 'Kharoshthi',
'Khmr': 'Khmer',
'Khoj': 'Khojki',
+ 'Kits': 'Khitan_Small_Script',
'Knda': 'Kannada',
'Kthi': 'Kaithi',
'Lana': 'Tai_Tham',
@@ -3298,6 +3353,7 @@ NAMES = {
'Wcho': 'Wancho',
'Xpeo': 'Old_Persian',
'Xsux': 'Cuneiform',
+ 'Yezi': 'Yezidi',
'Yiii': 'Yi',
'Zanb': 'Zanabazar_Square',
'Zinh': 'Inherited',
diff --git a/Lib/fontTools/unicodedata/__init__.py b/Lib/fontTools/unicodedata/__init__.py
index 79462c76..8845b829 100644
--- a/Lib/fontTools/unicodedata/__init__.py
+++ b/Lib/fontTools/unicodedata/__init__.py
@@ -1,6 +1,4 @@
-from __future__ import (
- print_function, division, absolute_import, unicode_literals)
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import byteord, tostr
import re
from bisect import bisect_right
@@ -52,7 +50,7 @@ def script(char):
'Latn'
>>> script(",")
'Zyyy'
- >>> script(unichr(0x10FFFF))
+ >>> script(chr(0x10FFFF))
'Zzzz'
"""
code = byteord(char)
@@ -75,9 +73,9 @@ def script_extension(char):
>>> script_extension("a") == {'Latn'}
True
- >>> script_extension(unichr(0x060C)) == {'Arab', 'Rohg', 'Syrc', 'Thaa'}
+ >>> script_extension(chr(0x060C)) == {'Rohg', 'Syrc', 'Yezi', 'Arab', 'Thaa'}
True
- >>> script_extension(unichr(0x10FFFF)) == {'Zzzz'}
+ >>> script_extension(chr(0x10FFFF)) == {'Zzzz'}
True
"""
code = byteord(char)
@@ -136,10 +134,8 @@ def script_code(script_name, default=KeyError):
return default
-# The data on script direction is taken from harfbuzz's "hb-common.cc":
-# https://goo.gl/X5FDXC
-# It matches the CLDR "scriptMetadata.txt as of January 2018:
-# http://unicode.org/repos/cldr/trunk/common/properties/scriptMetadata.txt
+# The data on script direction is taken from CLDR 37:
+# https://github.com/unicode-org/cldr/blob/release-37/common/properties/scriptMetadata.txt
RTL_SCRIPTS = {
# Unicode-1.1 additions
'Arab', # Arabic
@@ -192,6 +188,18 @@ RTL_SCRIPTS = {
# Unicode-9.0 additions
'Adlm', # Adlam
+
+ # Unicode-11.0 additions
+ 'Rohg', # Hanifi Rohingya
+ 'Sogo', # Old Sogdian
+ 'Sogd', # Sogdian
+
+ # Unicode-12.0 additions
+ 'Elym', # Elymaic
+
+ # Unicode-13.0 additions
+ 'Chrs', # Chorasmian
+ 'Yezi', # Yezidi
}
def script_horizontal_direction(script_code, default=KeyError):
@@ -211,9 +219,9 @@ def block(char):
>>> block("a")
'Basic Latin'
- >>> block(unichr(0x060C))
+ >>> block(chr(0x060C))
'Arabic'
- >>> block(unichr(0xEFFFF))
+ >>> block(chr(0xEFFFF))
'No_Block'
"""
code = byteord(char)
diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py
index 5a881495..36ff0d97 100644
--- a/Lib/fontTools/varLib/__init__.py
+++ b/Lib/fontTools/varLib/__init__.py
@@ -18,11 +18,9 @@ Then you can make a variable-font this way:
API *will* change in near future.
"""
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
-from fontTools.misc.arrayTools import Vector
+from fontTools.misc.py23 import Tag, tostr
+from fontTools.misc.roundTools import noRound, otRound
+from fontTools.misc.vector import Vector
from fontTools.ttLib import TTFont, newTable
from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
@@ -36,17 +34,20 @@ from fontTools.varLib.mvar import MVAR_ENTRIES
from fontTools.varLib.iup import iup_delta_optimize
from fontTools.varLib.featureVars import addFeatureVariations
from fontTools.designspaceLib import DesignSpaceDocument
+from functools import partial
from collections import OrderedDict, namedtuple
import os.path
import logging
from copy import deepcopy
from pprint import pformat
+from .errors import VarLibError, VarLibValidationError
log = logging.getLogger("fontTools.varLib")
-
-class VarLibError(Exception):
- pass
+# This is a lib key for the designspace document. The value should be
+# an OpenType feature tag, to be used as the FeatureVariations feature.
+# If present, the DesignSpace <rules processing="..."> flag is ignored.
+FEAVAR_FEATURETAG_LIB_KEY = "com.github.fonttools.varLib.featureVarsFeatureTag"
#
# Creation routines
@@ -75,7 +76,7 @@ def _add_fvar(font, axes, instances):
axis.axisTag = Tag(a.tag)
# TODO Skip axes that have no variation.
axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum
- axis.axisNameID = nameTable.addMultilingualName(a.labelNames, font)
+ axis.axisNameID = nameTable.addMultilingualName(a.labelNames, font, minNameID=256)
axis.flags = int(a.hidden)
fvar.axes.append(axis)
@@ -83,9 +84,14 @@ def _add_fvar(font, axes, instances):
coordinates = instance.location
if "en" not in instance.localisedStyleName:
- assert instance.styleName
+ if not instance.styleName:
+ raise VarLibValidationError(
+ f"Instance at location '{coordinates}' must have a default English "
+ "style name ('stylename' attribute on the instance element or a "
+ "stylename element with an 'xml:lang=\"en\"' attribute)."
+ )
localisedStyleName = dict(instance.localisedStyleName)
- localisedStyleName["en"] = tounicode(instance.styleName)
+ localisedStyleName["en"] = tostr(instance.styleName)
else:
localisedStyleName = instance.localisedStyleName
@@ -94,7 +100,7 @@ def _add_fvar(font, axes, instances):
inst = NamedInstance()
inst.subfamilyNameID = nameTable.addMultilingualName(localisedStyleName)
if psname is not None:
- psname = tounicode(psname)
+ psname = tostr(psname)
inst.postscriptNameID = nameTable.addName(psname)
inst.coordinates = {axes[k].tag:axes[k].map_backward(v) for k,v in coordinates.items()}
#inst.coordinates = {axes[k].tag:v for k,v in coordinates.items()}
@@ -139,14 +145,32 @@ def _add_avar(font, axes):
# Current avar requirements. We don't have to enforce
# these on the designer and can deduce some ourselves,
# but for now just enforce them.
- assert axis.minimum == min(keys)
- assert axis.maximum == max(keys)
- assert axis.default in keys
- # No duplicates
- assert len(set(keys)) == len(keys)
- assert len(set(vals)) == len(vals)
+ if axis.minimum != min(keys):
+ raise VarLibValidationError(
+ f"Axis '{axis.name}': there must be a mapping for the axis minimum "
+ f"value {axis.minimum} and it must be the lowest input mapping value."
+ )
+ if axis.maximum != max(keys):
+ raise VarLibValidationError(
+ f"Axis '{axis.name}': there must be a mapping for the axis maximum "
+ f"value {axis.maximum} and it must be the highest input mapping value."
+ )
+ if axis.default not in keys:
+ raise VarLibValidationError(
+ f"Axis '{axis.name}': there must be a mapping for the axis default "
+ f"value {axis.default}."
+ )
+ # No duplicate input values (output values can be >= their preceeding value).
+ if len(set(keys)) != len(keys):
+ raise VarLibValidationError(
+ f"Axis '{axis.name}': All axis mapping input='...' values must be "
+ "unique, but we found duplicates."
+ )
# Ascending values
- assert sorted(vals) == vals
+ if sorted(vals) != vals:
+ raise VarLibValidationError(
+ f"Axis '{axis.name}': mapping output values must be in ascending order."
+ )
keys_triple = (axis.minimum, axis.default, axis.maximum)
vals_triple = tuple(axis.map_forward(v) for v in keys_triple)
@@ -183,44 +207,21 @@ def _add_stat(font, axes):
if "STAT" in font:
return
+ from ..otlLib.builder import buildStatTable
fvarTable = font['fvar']
-
- STAT = font["STAT"] = newTable('STAT')
- stat = STAT.table = ot.STAT()
- stat.Version = 0x00010001
-
- axisRecords = []
- for i, a in enumerate(fvarTable.axes):
- axis = ot.AxisRecord()
- axis.AxisTag = Tag(a.axisTag)
- axis.AxisNameID = a.axisNameID
- axis.AxisOrdering = i
- axisRecords.append(axis)
-
- axisRecordArray = ot.AxisRecordArray()
- axisRecordArray.Axis = axisRecords
- # XXX these should not be hard-coded but computed automatically
- stat.DesignAxisRecordSize = 8
- stat.DesignAxisCount = len(axisRecords)
- stat.DesignAxisRecord = axisRecordArray
-
- # for the elided fallback name, we default to the base style name.
- # TODO make this user-configurable via designspace document
- stat.ElidedFallbackNameID = 2
+ axes = [dict(tag=a.axisTag, name=a.axisNameID) for a in fvarTable.axes]
+ buildStatTable(font, axes)
def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
-
- assert tolerance >= 0
+ if tolerance < 0:
+ raise ValueError("`tolerance` must be a positive number.")
log.info("Generating gvar")
assert "gvar" not in font
gvar = font["gvar"] = newTable('gvar')
- gvar.version = 1
- gvar.reserved = 0
- gvar.variations = {}
-
glyf = font['glyf']
+ defaultMasterIndex = masterModel.reverseMapping[0]
# use hhea.ascent of base master as default vertical origin when vmtx is missing
baseAscent = font['hhea'].ascent
@@ -232,6 +233,15 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
m["glyf"].getCoordinatesAndControls(glyph, m, defaultVerticalOrigin=baseAscent)
for m in master_ttfs
]
+
+ if allData[defaultMasterIndex][1].numberOfContours != 0:
+ # If the default master is not empty, interpret empty non-default masters
+ # as missing glyphs from a sparse master
+ allData = [
+ d if d is not None and d[1].numberOfContours != 0 else None
+ for d in allData
+ ]
+
model, allData = masterModel.getSubModel(allData)
allCoords = [d[0] for d in allData]
@@ -244,7 +254,7 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
# Update gvar
gvar.variations[glyph] = []
- deltas = model.getDeltas(allCoords)
+ deltas = model.getDeltas(allCoords, round=partial(GlyphCoordinates.__round__, round=round))
supports = model.supports
assert len(deltas) == len(supports)
@@ -253,7 +263,7 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
endPts = control.endPts
for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
- if all(abs(v) <= tolerance for v in delta.array) and not isComposite:
+ if all(v == 0 for v in delta.array) and not isComposite:
continue
var = TupleVariation(support, delta)
if optimize:
@@ -284,6 +294,7 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
gvar.variations[glyph].append(var)
+
def _remove_TTHinting(font):
for tag in ("cvar", "cvt ", "fpgm", "prep"):
if tag in font:
@@ -294,7 +305,7 @@ def _remove_TTHinting(font):
font["glyf"].removeHinting()
# TODO: Modify gasp table to deactivate gridfitting for all ranges?
-def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5):
+def _merge_TTHinting(font, masterModel, master_ttfs):
log.info("Merging TT hinting")
assert "cvar" not in font
@@ -352,19 +363,20 @@ def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5):
_remove_TTHinting(font)
return
- # We can build the cvar table now.
-
- cvar = font["cvar"] = newTable('cvar')
- cvar.version = 1
- cvar.variations = []
-
- deltas, supports = masterModel.getDeltasAndSupports(all_cvs)
+ variations = []
+ deltas, supports = masterModel.getDeltasAndSupports(all_cvs, round=round) # builtin round calls into Vector.__round__, which uses builtin round as we like
for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
- delta = [otRound(d) for d in delta]
- if all(abs(v) <= tolerance for v in delta):
+ if all(v == 0 for v in delta):
continue
var = TupleVariation(support, delta)
- cvar.variations.append(var)
+ variations.append(var)
+
+ # We can build the cvar table now.
+ if variations:
+ cvar = font["cvar"] = newTable('cvar')
+ cvar.version = 1
+ cvar.variations = variations
+
_MetricsFields = namedtuple('_MetricsFields',
['tableTag', 'metricsTag', 'sb1', 'sb2', 'advMapping', 'vOrigMapping'])
@@ -429,7 +441,7 @@ def _get_advance_metrics(font, masterModel, master_ttfs,
vOrigDeltasAndSupports = {}
for glyph in glyphOrder:
vhAdvances = [metrics[glyph][0] if glyph in metrics else None for metrics in advMetricses]
- vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vhAdvances)
+ vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vhAdvances, round=round)
singleModel = models.allEqual(id(v[1]) for v in vhAdvanceDeltasAndSupports.values())
@@ -441,7 +453,7 @@ def _get_advance_metrics(font, masterModel, master_ttfs,
# glyphs which have a non-default vOrig.
vOrigs = [metrics[glyph] if glyph in metrics else defaultVOrig
for metrics, defaultVOrig in vOrigMetricses]
- vOrigDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vOrigs)
+ vOrigDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vOrigs, round=round)
directStore = None
if singleModel:
@@ -451,7 +463,7 @@ def _get_advance_metrics(font, masterModel, master_ttfs,
varTupleIndexes = list(range(len(supports)))
varData = builder.buildVarData(varTupleIndexes, [], optimize=False)
for glyphName in glyphOrder:
- varData.addItem(vhAdvanceDeltasAndSupports[glyphName][0])
+ varData.addItem(vhAdvanceDeltasAndSupports[glyphName][0], round=noRound)
varData.optimize()
directStore = builder.buildVarStore(varTupleList, [varData])
@@ -461,14 +473,14 @@ def _get_advance_metrics(font, masterModel, master_ttfs,
for glyphName in glyphOrder:
deltas, supports = vhAdvanceDeltasAndSupports[glyphName]
storeBuilder.setSupports(supports)
- advMapping[glyphName] = storeBuilder.storeDeltas(deltas)
+ advMapping[glyphName] = storeBuilder.storeDeltas(deltas, round=noRound)
if vOrigMetricses:
vOrigMap = {}
for glyphName in glyphOrder:
deltas, supports = vOrigDeltasAndSupports[glyphName]
storeBuilder.setSupports(supports)
- vOrigMap[glyphName] = storeBuilder.storeDeltas(deltas)
+ vOrigMap[glyphName] = storeBuilder.storeDeltas(deltas, round=noRound)
indirectStore = storeBuilder.finish()
mapping2 = indirectStore.optimize()
@@ -583,6 +595,22 @@ def _add_MVAR(font, masterModel, master_ttfs, axisTags):
mvar.ValueRecord = sorted(records, key=lambda r: r.ValueTag)
+def _add_BASE(font, masterModel, master_ttfs, axisTags):
+
+ log.info("Generating BASE")
+
+ merger = VariationMerger(masterModel, axisTags, font)
+ merger.mergeTables(font, master_ttfs, ['BASE'])
+ store = merger.store_builder.finish()
+
+ if not store.VarData:
+ return
+ base = font['BASE'].table
+ assert base.Version == 0x00010000
+ base.Version = 0x00010001
+ base.VarStore = store
+
+
def _merge_OTL(font, model, master_fonts, axisTags):
log.info("Merging OpenType Layout tables")
@@ -615,7 +643,7 @@ def _merge_OTL(font, model, master_fonts, axisTags):
font['GPOS'].table.remap_device_varidxes(varidx_map)
-def _add_GSUB_feature_variations(font, axes, internal_axis_supports, rules):
+def _add_GSUB_feature_variations(font, axes, internal_axis_supports, rules, featureTag):
def normalize(name, value):
return models.normalizeLocation(
@@ -650,7 +678,7 @@ def _add_GSUB_feature_variations(font, axes, internal_axis_supports, rules):
conditional_subs.append((region, subs))
- addFeatureVariations(font, conditional_subs)
+ addFeatureVariations(font, conditional_subs, featureTag)
_DesignSpaceData = namedtuple(
@@ -663,14 +691,18 @@ _DesignSpaceData = namedtuple(
"masters",
"instances",
"rules",
+ "rulesProcessingLast",
+ "lib",
],
)
def _add_CFF2(varFont, model, master_fonts):
- from .cff import (convertCFFtoCFF2, merge_region_fonts)
+ from .cff import merge_region_fonts
glyphOrder = varFont.getGlyphOrder()
- convertCFFtoCFF2(varFont)
+ if "CFF2" not in varFont:
+ from .cff import convertCFFtoCFF2
+ convertCFFtoCFF2(varFont)
ordered_fonts_list = model.reorderMasters(master_fonts, model.reverseMapping)
# re-ordering the master list simplifies building the CFF2 data item lists.
merge_region_fonts(varFont, model, ordered_fonts_list, glyphOrder)
@@ -686,9 +718,10 @@ def load_designspace(designspace):
masters = ds.sources
if not masters:
- raise VarLibError("no sources found in .designspace")
+ raise VarLibValidationError("Designspace must have at least one source.")
instances = ds.instances
+ # TODO: Use fontTools.designspaceLib.tagForAxisName instead.
standard_axis_map = OrderedDict([
('weight', ('wght', {'en': u'Weight'})),
('width', ('wdth', {'en': u'Width'})),
@@ -698,11 +731,15 @@ def load_designspace(designspace):
])
# Setup axes
+ if not ds.axes:
+ raise VarLibValidationError(f"Designspace must have at least one axis.")
+
axes = OrderedDict()
- for axis in ds.axes:
+ for axis_index, axis in enumerate(ds.axes):
axis_name = axis.name
if not axis_name:
- assert axis.tag is not None
+ if not axis.tag:
+ raise VarLibValidationError(f"Axis at index {axis_index} needs a tag.")
axis_name = axis.name = axis.tag
if axis_name in standard_axis_map:
@@ -711,9 +748,10 @@ def load_designspace(designspace):
if not axis.labelNames:
axis.labelNames.update(standard_axis_map[axis_name][1])
else:
- assert axis.tag is not None
+ if not axis.tag:
+ raise VarLibValidationError(f"Axis at index {axis_index} needs a tag.")
if not axis.labelNames:
- axis.labelNames["en"] = tounicode(axis_name)
+ axis.labelNames["en"] = tostr(axis_name)
axes[axis_name] = axis
log.info("Axes:\n%s", pformat([axis.asdict() for axis in axes.values()]))
@@ -722,14 +760,28 @@ def load_designspace(designspace):
for obj in masters+instances:
obj_name = obj.name or obj.styleName or ''
loc = obj.location
+ if loc is None:
+ raise VarLibValidationError(
+ f"Source or instance '{obj_name}' has no location."
+ )
for axis_name in loc.keys():
- assert axis_name in axes, "Location axis '%s' unknown for '%s'." % (axis_name, obj_name)
+ if axis_name not in axes:
+ raise VarLibValidationError(
+ f"Location axis '{axis_name}' unknown for '{obj_name}'."
+ )
for axis_name,axis in axes.items():
if axis_name not in loc:
- loc[axis_name] = axis.default
+ # NOTE: `axis.default` is always user-space, but `obj.location` always design-space.
+ loc[axis_name] = axis.map_forward(axis.default)
else:
v = axis.map_backward(loc[axis_name])
- assert axis.minimum <= v <= axis.maximum, "Location for axis '%s' (mapped to %s) out of range for '%s' [%s..%s]" % (axis_name, v, obj_name, axis.minimum, axis.maximum)
+ if not (axis.minimum <= v <= axis.maximum):
+ raise VarLibValidationError(
+ f"Source or instance '{obj_name}' has out-of-range location "
+ f"for axis '{axis_name}': is mapped to {v} but must be in "
+ f"mapped range [{axis.minimum}..{axis.maximum}] (NOTE: all "
+ "values are in user-space)."
+ )
# Normalize master locations
@@ -750,9 +802,15 @@ def load_designspace(designspace):
base_idx = None
for i,m in enumerate(normalized_master_locs):
if all(v == 0 for v in m.values()):
- assert base_idx is None
+ if base_idx is not None:
+ raise VarLibValidationError(
+ "More than one base master found in Designspace."
+ )
base_idx = i
- assert base_idx is not None, "Base master not found; no master at default location?"
+ if base_idx is None:
+ raise VarLibValidationError(
+ "Base master not found; no master at default location?"
+ )
log.info("Index of base master: %s", base_idx)
return _DesignSpaceData(
@@ -763,6 +821,8 @@ def load_designspace(designspace):
masters,
instances,
ds.rules,
+ ds.rulesProcessingLast,
+ ds.lib,
)
@@ -785,7 +845,7 @@ def set_default_weight_width_slant(font, location):
if "wght" in location:
weight_class = otRound(max(1, min(location["wght"], 1000)))
if font["OS/2"].usWeightClass != weight_class:
- log.info("Setting OS/2.usWidthClass = %s", weight_class)
+ log.info("Setting OS/2.usWeightClass = %s", weight_class)
font["OS/2"].usWeightClass = weight_class
if "wdth" in location:
@@ -854,6 +914,8 @@ def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
assert 0 == model.mapping[ds.base_idx]
log.info("Building variations tables")
+ if 'BASE' not in exclude and 'BASE' in vf:
+ _add_BASE(vf, model, master_fonts, axisTags)
if 'MVAR' not in exclude:
_add_MVAR(vf, model, master_fonts, axisTags)
if 'HVAR' not in exclude:
@@ -867,8 +929,12 @@ def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
if 'cvar' not in exclude and 'glyf' in vf:
_merge_TTHinting(vf, model, master_fonts)
if 'GSUB' not in exclude and ds.rules:
- _add_GSUB_feature_variations(vf, ds.axes, ds.internal_axis_supports, ds.rules)
- if 'CFF2' not in exclude and 'CFF ' in vf:
+ featureTag = ds.lib.get(
+ FEAVAR_FEATURETAG_LIB_KEY,
+ "rclt" if ds.rulesProcessingLast else "rvrn"
+ )
+ _add_GSUB_feature_variations(vf, ds.axes, ds.internal_axis_supports, ds.rules, featureTag)
+ if 'CFF2' not in exclude and ('CFF ' in vf or 'CFF2' in vf):
_add_CFF2(vf, model, master_fonts)
if "post" in vf:
# set 'post' to format 2 to keep the glyph names dropped from CFF2
@@ -908,7 +974,7 @@ def _open_font(path, master_finder=lambda s: s):
elif tp in ("TTF", "OTF", "WOFF", "WOFF2"):
font = TTFont(master_path)
else:
- raise VarLibError("Invalid master path: %r" % master_path)
+ raise VarLibValidationError("Invalid master path: %r" % master_path)
return font
@@ -928,10 +994,10 @@ def load_masters(designspace, master_finder=lambda s: s):
# If a SourceDescriptor has a layer name, demand that the compiled TTFont
# be supplied by the caller. This spares us from modifying MasterFinder.
if master.layerName and master.font is None:
- raise AttributeError(
- "Designspace source '%s' specified a layer name but lacks the "
- "required TTFont object in the 'font' attribute."
- % (master.name or "<Unknown>")
+ raise VarLibValidationError(
+ f"Designspace source '{master.name or '<Unknown>'}' specified a "
+ "layer name but lacks the required TTFont object in the 'font' "
+ "attribute."
)
return designspace.loadSourceFonts(_open_font, master_finder=master_finder)
@@ -957,10 +1023,11 @@ class MasterFinder(object):
def main(args=None):
+ """Build a variable font from a designspace file and masters"""
from argparse import ArgumentParser
from fontTools import configLogger
- parser = ArgumentParser(prog='varLib')
+ parser = ArgumentParser(prog='varLib', description = main.__doc__)
parser.add_argument('designspace')
parser.add_argument(
'-o',
diff --git a/Lib/fontTools/varLib/__main__.py b/Lib/fontTools/varLib/__main__.py
index 5cf05e52..4b3a0f53 100644
--- a/Lib/fontTools/varLib/__main__.py
+++ b/Lib/fontTools/varLib/__main__.py
@@ -1,7 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import sys
from fontTools.varLib import main
+
if __name__ == '__main__':
sys.exit(main())
diff --git a/Lib/fontTools/varLib/builder.py b/Lib/fontTools/varLib/builder.py
index 43fb92a1..152336b0 100644
--- a/Lib/fontTools/varLib/builder.py
+++ b/Lib/fontTools/varLib/builder.py
@@ -1,4 +1,3 @@
-from __future__ import print_function, division, absolute_import
from fontTools import ttLib
from fontTools.ttLib.tables import otTables as ot
diff --git a/Lib/fontTools/varLib/cff.py b/Lib/fontTools/varLib/cff.py
index aaabf857..4eed8b33 100644
--- a/Lib/fontTools/varLib/cff.py
+++ b/Lib/fontTools/varLib/cff.py
@@ -1,5 +1,4 @@
from collections import namedtuple
-import os
from fontTools.cffLib import (
maxStackLimit,
TopDictIndex,
@@ -12,14 +11,25 @@ from fontTools.cffLib import (
FontDict,
VarStoreData
)
-from fontTools.misc.py23 import BytesIO
+from io import BytesIO
from fontTools.cffLib.specializer import (
specializeCommands, commandsToProgram)
from fontTools.ttLib import newTable
from fontTools import varLib
from fontTools.varLib.models import allEqual
+from fontTools.misc.roundTools import roundFunc
from fontTools.misc.psCharStrings import T2CharString, T2OutlineExtractor
-from fontTools.pens.t2CharStringPen import T2CharStringPen, t2c_round
+from fontTools.pens.t2CharStringPen import T2CharStringPen
+from functools import partial
+
+from .errors import (
+ VarLibCFFDictMergeError, VarLibCFFPointTypeMergeError,
+ VarLibCFFHintTypeMergeError,VarLibMergeError)
+
+
+# Backwards compatibility
+MergeDictError = VarLibCFFDictMergeError
+MergeTypeError = VarLibCFFPointTypeMergeError
def addCFFVarStore(varFont, varModel, varDataList, masterSupports):
@@ -30,6 +40,11 @@ def addCFFVarStore(varFont, varModel, varDataList, masterSupports):
topDict = varFont['CFF2'].cff.topDictIndex[0]
topDict.VarStore = VarStoreData(otVarStore=varStoreCFFV)
+ if topDict.FDArray[0].vstore is None:
+ fdArray = topDict.FDArray
+ for fontDict in fdArray:
+ if hasattr(fontDict, "Private"):
+ fontDict.Private.vstore = topDict.VarStore
def lib_convertCFFToCFF2(cff, otFont):
@@ -61,15 +76,16 @@ def lib_convertCFFToCFF2(cff, otFont):
fdArray.append(fontDict)
fontDict.Private = privateDict
privateOpOrder = buildOrder(privateDictOperators2)
- for entry in privateDictOperators:
- key = entry[1]
- if key not in privateOpOrder:
- if key in privateDict.rawDict:
- # print "Removing private dict", key
- del privateDict.rawDict[key]
- if hasattr(privateDict, key):
- delattr(privateDict, key)
- # print "Removing privateDict attr", key
+ if privateDict is not None:
+ for entry in privateDictOperators:
+ key = entry[1]
+ if key not in privateOpOrder:
+ if key in privateDict.rawDict:
+ # print "Removing private dict", key
+ del privateDict.rawDict[key]
+ if hasattr(privateDict, key):
+ delattr(privateDict, key)
+ # print "Removing privateDict attr", key
else:
# clean up the PrivateDicts in the fdArray
fdArray = topDict.FDArray
@@ -120,16 +136,6 @@ def convertCFFtoCFF2(varFont):
del varFont['CFF ']
-class MergeDictError(TypeError):
- def __init__(self, key, value, values):
- error_msg = ["For the Private Dict key '{}', ".format(key),
- "the default font value list:",
- "\t{}".format(value),
- "had a different number of values than a region font:"]
- error_msg += ["\t{}".format(region_value) for region_value in values]
- error_msg = os.linesep.join(error_msg)
-
-
def conv_to_int(num):
if isinstance(num, float) and num.is_integer():
return int(num)
@@ -213,7 +219,7 @@ def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map):
try:
values = zip(*values)
except IndexError:
- raise MergeDictError(key, value, values)
+ raise VarLibCFFDictMergeError(key, value, values)
"""
Row 0 contains the first value from each master.
Convert each row from absolute values to relative
@@ -265,6 +271,12 @@ def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map):
private_dict.rawDict[key] = dataList
+def _cff_or_cff2(font):
+ if "CFF " in font:
+ return font["CFF "]
+ return font["CFF2"]
+
+
def getfd_map(varFont, fonts_list):
""" Since a subset source font may have fewer FontDicts in their
FDArray than the default font, we have to match up the FontDicts in
@@ -277,7 +289,7 @@ def getfd_map(varFont, fonts_list):
default_font = fonts_list[0]
region_fonts = fonts_list[1:]
num_regions = len(region_fonts)
- topDict = default_font['CFF '].cff.topDictIndex[0]
+ topDict = _cff_or_cff2(default_font).cff.topDictIndex[0]
if not hasattr(topDict, 'FDSelect'):
# All glyphs reference only one FontDict.
# Map the FD index for regions to index 0.
@@ -293,7 +305,7 @@ def getfd_map(varFont, fonts_list):
fd_map[fdIndex] = {}
for ri, region_font in enumerate(region_fonts):
region_glyphOrder = region_font.getGlyphOrder()
- region_topDict = region_font['CFF '].cff.topDictIndex[0]
+ region_topDict = _cff_or_cff2(region_font).cff.topDictIndex[0]
if not hasattr(region_topDict, 'FDSelect'):
# All the glyphs share the same FontDict. Pick any glyph.
default_fdIndex = gname_mapping[region_glyphOrder[0]]
@@ -312,7 +324,7 @@ CVarData = namedtuple('CVarData', 'varDataList masterSupports vsindex_dict')
def merge_region_fonts(varFont, model, ordered_fonts_list, glyphOrder):
topDict = varFont['CFF2'].cff.topDictIndex[0]
top_dicts = [topDict] + [
- ttFont['CFF '].cff.topDictIndex[0]
+ _cff_or_cff2(ttFont).cff.topDictIndex[0]
for ttFont in ordered_fonts_list[1:]
]
num_masters = len(model.mapping)
@@ -405,7 +417,7 @@ def merge_charstrings(glyphOrder, num_masters, top_dicts, masterModel):
# in the PrivatDict, so we will build the default data for vsindex = 0.
if not vsindex_dict:
key = (True,) * num_masters
- _add_new_vsindex(model, key, masterSupports, vsindex_dict,
+ _add_new_vsindex(masterModel, key, masterSupports, vsindex_dict,
vsindex_by_key, varDataList)
cvData = CVarData(varDataList=varDataList, masterSupports=masterSupports,
vsindex_dict=vsindex_dict)
@@ -414,31 +426,6 @@ def merge_charstrings(glyphOrder, num_masters, top_dicts, masterModel):
return cvData
-class MergeTypeError(TypeError):
- def __init__(self, point_type, pt_index, m_index, default_type, glyphName):
- self.error_msg = [
- "In glyph '{gname}' "
- "'{point_type}' at point index {pt_index} in master "
- "index {m_index} differs from the default font point "
- "type '{default_type}'"
- "".format(
- gname=glyphName,
- point_type=point_type, pt_index=pt_index,
- m_index=m_index, default_type=default_type)
- ][0]
- super(MergeTypeError, self).__init__(self.error_msg)
-
-
-def makeRoundNumberFunc(tolerance):
- if tolerance < 0:
- raise ValueError("Rounding tolerance must be positive")
-
- def roundNumber(val):
- return t2c_round(val, tolerance)
-
- return roundNumber
-
-
class CFFToCFF2OutlineExtractor(T2OutlineExtractor):
""" This class is used to remove the initial width from the CFF
charstring without trying to add the width to self.nominalWidthX,
@@ -460,7 +447,7 @@ class MergeOutlineExtractor(CFFToCFF2OutlineExtractor):
def __init__(self, pen, localSubrs, globalSubrs,
nominalWidthX, defaultWidthX, private=None):
- super(CFFToCFF2OutlineExtractor, self).__init__(pen, localSubrs,
+ super().__init__(pen, localSubrs,
globalSubrs, nominalWidthX, defaultWidthX, private)
def countHints(self):
@@ -514,9 +501,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
def __init__(
self, default_commands, glyphName, num_masters, master_idx,
roundTolerance=0.5):
- super(
- CFF2CharStringMergePen,
- self).__init__(
+ super().__init__(
width=None,
glyphSet=None, CFF2=True,
roundTolerance=roundTolerance)
@@ -527,7 +512,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
self.prev_move_idx = 0
self.seen_moveto = False
self.glyphName = glyphName
- self.roundNumber = makeRoundNumberFunc(roundTolerance)
+ self.round = roundFunc(roundTolerance, round=round)
def add_point(self, point_type, pt_coords):
if self.m_index == 0:
@@ -535,7 +520,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
else:
cmd = self._commands[self.pt_index]
if cmd[0] != point_type:
- raise MergeTypeError(
+ raise VarLibCFFPointTypeMergeError(
point_type,
self.pt_index, len(cmd[1]),
cmd[0], self.glyphName)
@@ -548,7 +533,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
else:
cmd = self._commands[self.pt_index]
if cmd[0] != hint_type:
- raise MergeTypeError(hint_type, self.pt_index, len(cmd[1]),
+ raise VarLibCFFHintTypeMergeError(hint_type, self.pt_index, len(cmd[1]),
cmd[0], self.glyphName)
cmd[1].append(args)
self.pt_index += 1
@@ -557,14 +542,14 @@ class CFF2CharStringMergePen(T2CharStringPen):
# For hintmask, fonttools.cffLib.specializer.py expects
# each of these to be represented by two sequential commands:
# first holding only the operator name, with an empty arg list,
- # second with an empty string as the op name, and the mask arg list.
+ # second with an empty string as the op name, and the mask arg list.
if self.m_index == 0:
self._commands.append([hint_type, []])
self._commands.append(["", [abs_args]])
else:
cmd = self._commands[self.pt_index]
if cmd[0] != hint_type:
- raise MergeTypeError(hint_type, self.pt_index, len(cmd[1]),
+ raise VarLibCFFHintTypeMergeError(hint_type, self.pt_index, len(cmd[1]),
cmd[0], self.glyphName)
self.pt_index += 1
cmd = self._commands[self.pt_index]
@@ -603,7 +588,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
def getCommands(self):
return self._commands
- def reorder_blend_args(self, commands, get_delta_func, round_func):
+ def reorder_blend_args(self, commands, get_delta_func):
"""
We first re-order the master coordinate values.
For a moveto to lineto, the args are now arranged as:
@@ -634,8 +619,8 @@ class CFF2CharStringMergePen(T2CharStringPen):
# second has only args.
if lastOp in ['hintmask', 'cntrmask']:
coord = list(cmd[1])
- assert allEqual(coord), (
- "hintmask values cannot differ between source fonts.")
+ if not allEqual(coord):
+ raise VarLibMergeError("Hintmask values cannot differ between source fonts.")
cmd[1] = [coord[0][0]]
else:
coords = cmd[1]
@@ -646,8 +631,6 @@ class CFF2CharStringMergePen(T2CharStringPen):
else:
# convert to deltas
deltas = get_delta_func(coord)[1:]
- if round_func:
- deltas = [round_func(delta) for delta in deltas]
coord = [coord[0]] + deltas
new_coords.append(coord)
cmd[1] = new_coords
@@ -658,8 +641,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
self, private=None, globalSubrs=None,
var_model=None, optimize=True):
commands = self._commands
- commands = self.reorder_blend_args(commands, var_model.getDeltas,
- self.roundNumber)
+ commands = self.reorder_blend_args(commands, partial (var_model.getDeltas, round=self.round))
if optimize:
commands = specializeCommands(
commands, generalizeFirst=False,
diff --git a/Lib/fontTools/varLib/errors.py b/Lib/fontTools/varLib/errors.py
new file mode 100644
index 00000000..5840070f
--- /dev/null
+++ b/Lib/fontTools/varLib/errors.py
@@ -0,0 +1,190 @@
+import textwrap
+
+
+class VarLibError(Exception):
+ """Base exception for the varLib module."""
+
+
+class VarLibValidationError(VarLibError):
+ """Raised when input data is invalid from varLib's point of view."""
+
+
+class VarLibMergeError(VarLibError):
+ """Raised when input data cannot be merged into a variable font."""
+
+ def __init__(self, merger, **kwargs):
+ self.merger = merger
+ if not kwargs:
+ kwargs = {}
+ if "stack" in kwargs:
+ self.stack = kwargs["stack"]
+ del kwargs["stack"]
+ else:
+ self.stack = []
+ self.cause = kwargs
+
+ @property
+ def reason(self):
+ return self.__doc__
+
+ def _master_name(self, ix):
+ ttf = self.merger.ttfs[ix]
+ if (
+ "name" in ttf
+ and ttf["name"].getDebugName(1)
+ and ttf["name"].getDebugName(2)
+ ):
+ return ttf["name"].getDebugName(1) + " " + ttf["name"].getDebugName(2)
+ elif hasattr(ttf.reader, "file") and hasattr(ttf.reader.file, "name"):
+ return ttf.reader.file.name
+ else:
+ return "master number %i" % ix
+
+ @property
+ def offender(self):
+ if "expected" in self.cause and "got" in self.cause:
+ index = [x == self.cause["expected"] for x in self.cause["got"]].index(
+ False
+ )
+ return index, self._master_name(index)
+ return None, None
+
+ @property
+ def details(self):
+ if "expected" in self.cause and "got" in self.cause:
+ offender_index, offender = self.offender
+ got = self.cause["got"][offender_index]
+ return f"Expected to see {self.stack[0]}=={self.cause['expected']}, instead saw {got}\n"
+ return ""
+
+ def __str__(self):
+ offender_index, offender = self.offender
+ location = ""
+ if offender:
+ location = f"\n\nThe problem is likely to be in {offender}:\n"
+ context = "".join(reversed(self.stack))
+ basic = textwrap.fill(
+ f"Couldn't merge the fonts, because {self.reason}. "
+ f"This happened while performing the following operation: {context}",
+ width=78,
+ )
+ return "\n\n" + basic + location + self.details
+
+
+class ShouldBeConstant(VarLibMergeError):
+ """some values were different, but should have been the same"""
+
+ @property
+ def details(self):
+ if self.stack[0] != ".FeatureCount":
+ return super().details
+ offender_index, offender = self.offender
+ bad_ttf = self.merger.ttfs[offender_index]
+ good_ttf = self.merger.ttfs[offender_index - 1]
+
+ good_features = [
+ x.FeatureTag
+ for x in good_ttf[self.stack[-1]].table.FeatureList.FeatureRecord
+ ]
+ bad_features = [
+ x.FeatureTag
+ for x in bad_ttf[self.stack[-1]].table.FeatureList.FeatureRecord
+ ]
+ return (
+ "\nIncompatible features between masters.\n"
+ f"Expected: {', '.join(good_features)}.\n"
+ f"Got: {', '.join(bad_features)}.\n"
+ )
+
+
+class FoundANone(VarLibMergeError):
+ """one of the values in a list was empty when it shouldn't have been"""
+
+ @property
+ def offender(self):
+ cause = self.argv[0]
+ index = [x is None for x in cause["got"]].index(True)
+ return index, self._master_name(index)
+
+ @property
+ def details(self):
+ cause, stack = self.args[0], self.args[1:]
+ return f"{stack[0]}=={cause['got']}\n"
+
+
+class MismatchedTypes(VarLibMergeError):
+ """data had inconsistent types"""
+
+
+class LengthsDiffer(VarLibMergeError):
+ """a list of objects had inconsistent lengths"""
+
+
+class KeysDiffer(VarLibMergeError):
+ """a list of objects had different keys"""
+
+
+class InconsistentGlyphOrder(VarLibMergeError):
+ """the glyph order was inconsistent between masters"""
+
+
+class InconsistentExtensions(VarLibMergeError):
+ """the masters use extension lookups in inconsistent ways"""
+
+
+class UnsupportedFormat(VarLibMergeError):
+ """an OpenType subtable (%s) had a format I didn't expect"""
+
+ @property
+ def reason(self):
+ cause, stack = self.args[0], self.args[1:]
+ return self.__doc__ % cause["subtable"]
+
+
+class UnsupportedFormat(UnsupportedFormat):
+ """an OpenType subtable (%s) had inconsistent formats between masters"""
+
+
+class VarLibCFFMergeError(VarLibError):
+ pass
+
+
+class VarLibCFFDictMergeError(VarLibCFFMergeError):
+ """Raised when a CFF PrivateDict cannot be merged."""
+
+ def __init__(self, key, value, values):
+ error_msg = (
+ f"For the Private Dict key '{key}', the default font value list:"
+ f"\n\t{value}\nhad a different number of values than a region font:"
+ )
+ for region_value in values:
+ error_msg += f"\n\t{region_value}"
+ self.args = (error_msg,)
+
+
+class VarLibCFFPointTypeMergeError(VarLibCFFMergeError):
+ """Raised when a CFF glyph cannot be merged because of point type differences."""
+
+ def __init__(self, point_type, pt_index, m_index, default_type, glyph_name):
+ error_msg = (
+ f"Glyph '{glyph_name}': '{point_type}' at point index {pt_index} in "
+ f"master index {m_index} differs from the default font point type "
+ f"'{default_type}'"
+ )
+ self.args = (error_msg,)
+
+
+class VarLibCFFHintTypeMergeError(VarLibCFFMergeError):
+ """Raised when a CFF glyph cannot be merged because of hint type differences."""
+
+ def __init__(self, hint_type, cmd_index, m_index, default_type, glyph_name):
+ error_msg = (
+ f"Glyph '{glyph_name}': '{hint_type}' at index {cmd_index} in "
+ f"master index {m_index} differs from the default font hint type "
+ f"'{default_type}'"
+ )
+ self.args = (error_msg,)
+
+
+class VariationModelError(VarLibError):
+ """Raised when a variation model is faulty."""
diff --git a/Lib/fontTools/varLib/featureVars.py b/Lib/fontTools/varLib/featureVars.py
index 0c6913d3..45f3d839 100644
--- a/Lib/fontTools/varLib/featureVars.py
+++ b/Lib/fontTools/varLib/featureVars.py
@@ -3,8 +3,6 @@ https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#featurevariat
NOTE: The API is experimental and subject to change.
"""
-from __future__ import print_function, absolute_import, division
-from fontTools.misc.py23 import *
from fontTools.misc.dictTools import hashdict
from fontTools.misc.intTools import popCount
from fontTools.ttLib import newTable
@@ -12,8 +10,10 @@ from fontTools.ttLib.tables import otTables as ot
from fontTools.otlLib.builder import buildLookup, buildSingleSubstSubtable
from collections import OrderedDict
+from .errors import VarLibError, VarLibValidationError
-def addFeatureVariations(font, conditionalSubstitutions):
+
+def addFeatureVariations(font, conditionalSubstitutions, featureTag='rvrn'):
"""Add conditional substitutions to a Variable Font.
The `conditionalSubstitutions` argument is a list of (Region, Substitutions)
@@ -45,7 +45,8 @@ def addFeatureVariations(font, conditionalSubstitutions):
"""
addFeatureVariationsRaw(font,
- overlayFeatureVariations(conditionalSubstitutions))
+ overlayFeatureVariations(conditionalSubstitutions),
+ featureTag)
def overlayFeatureVariations(conditionalSubstitutions):
"""Compute overlaps between all conditional substitutions.
@@ -81,6 +82,7 @@ def overlayFeatureVariations(conditionalSubstitutions):
... ([{"wght": (0.5, 1.0)}], {"dollar": "dollar.rvrn"}),
... ([{"wght": (0.5, 1.0)}], {"dollar": "dollar.rvrn"}),
... ([{"wdth": (0.5, 1.0)}], {"cent": "cent.rvrn"}),
+ ... ([{"wght": (0.5, 1.0), "wdth": (-1, 1.0)}], {"dollar": "dollar.rvrn"}),
... ]
>>> from pprint import pprint
>>> pprint(overlayFeatureVariations(condSubst))
@@ -135,12 +137,14 @@ def overlayFeatureVariations(conditionalSubstitutions):
remainder = hashdict(remainder)
newMap[remainder] = newMap.get(remainder, 0) | rank
boxMap = newMap
- del boxMap[hashdict()]
# Generate output
items = []
for box,rank in sorted(boxMap.items(),
key=(lambda BoxAndRank: -popCount(BoxAndRank[1]))):
+ # Skip any box that doesn't have any substitution.
+ if rank == 0:
+ continue
substsList = []
i = 0
while rank:
@@ -257,15 +261,15 @@ def cleanupBox(box):
# Low level implementation
#
-def addFeatureVariationsRaw(font, conditionalSubstitutions):
+def addFeatureVariationsRaw(font, conditionalSubstitutions, featureTag='rvrn'):
"""Low level implementation of addFeatureVariations that directly
models the possibilities of the FeatureVariations table."""
#
- # assert there is no 'rvrn' feature
- # make dummy 'rvrn' feature with no lookups
- # sort features, get 'rvrn' feature index
- # add 'rvrn' feature to all scripts
+ # if there is no <featureTag> feature:
+ # make empty <featureTag> feature
+ # sort features, get <featureTag> feature index
+ # add <featureTag> feature to all scripts
# make lookups
# add feature variations
#
@@ -280,20 +284,30 @@ def addFeatureVariationsRaw(font, conditionalSubstitutions):
gsub.FeatureVariations = None # delete any existing FeatureVariations
- for feature in gsub.FeatureList.FeatureRecord:
- assert feature.FeatureTag != 'rvrn'
+ varFeatureIndices = []
+ for index, feature in enumerate(gsub.FeatureList.FeatureRecord):
+ if feature.FeatureTag == featureTag:
+ varFeatureIndices.append(index)
+
+ if not varFeatureIndices:
+ varFeature = buildFeatureRecord(featureTag, [])
+ gsub.FeatureList.FeatureRecord.append(varFeature)
+ gsub.FeatureList.FeatureCount = len(gsub.FeatureList.FeatureRecord)
- rvrnFeature = buildFeatureRecord('rvrn', [])
- gsub.FeatureList.FeatureRecord.append(rvrnFeature)
- gsub.FeatureList.FeatureCount = len(gsub.FeatureList.FeatureRecord)
+ sortFeatureList(gsub)
+ varFeatureIndex = gsub.FeatureList.FeatureRecord.index(varFeature)
- sortFeatureList(gsub)
- rvrnFeatureIndex = gsub.FeatureList.FeatureRecord.index(rvrnFeature)
+ for scriptRecord in gsub.ScriptList.ScriptRecord:
+ if scriptRecord.Script.DefaultLangSys is None:
+ raise VarLibError(
+ "Feature variations require that the script "
+ f"'{scriptRecord.ScriptTag}' defines a default language system."
+ )
+ langSystems = [lsr.LangSys for lsr in scriptRecord.Script.LangSysRecord]
+ for langSys in [scriptRecord.Script.DefaultLangSys] + langSystems:
+ langSys.FeatureIndex.append(varFeatureIndex)
- for scriptRecord in gsub.ScriptList.ScriptRecord:
- langSystems = [lsr.LangSys for lsr in scriptRecord.Script.LangSysRecord]
- for langSys in [scriptRecord.Script.DefaultLangSys] + langSystems:
- langSys.FeatureIndex.append(rvrnFeatureIndex)
+ varFeatureIndices = [varFeatureIndex]
# setup lookups
@@ -308,13 +322,19 @@ def addFeatureVariationsRaw(font, conditionalSubstitutions):
for conditionSet, substitutions in conditionalSubstitutions:
conditionTable = []
for axisTag, (minValue, maxValue) in sorted(conditionSet.items()):
- assert minValue < maxValue
+ if minValue > maxValue:
+ raise VarLibValidationError(
+ "A condition set has a minimum value above the maximum value."
+ )
ct = buildConditionTable(axisIndices[axisTag], minValue, maxValue)
conditionTable.append(ct)
lookupIndices = [lookupMap[subst] for subst in substitutions]
- record = buildFeatureTableSubstitutionRecord(rvrnFeatureIndex, lookupIndices)
- featureVariationRecords.append(buildFeatureVariationRecord(conditionTable, [record]))
+ records = []
+ for varFeatureIndex in varFeatureIndices:
+ existingLookupIndices = gsub.FeatureList.FeatureRecord[varFeatureIndex].Feature.LookupListIndex
+ records.append(buildFeatureTableSubstitutionRecord(varFeatureIndex, existingLookupIndices + lookupIndices))
+ featureVariationRecords.append(buildFeatureVariationRecord(conditionTable, records))
gsub.FeatureVariations = buildFeatureVariations(featureVariationRecords)
diff --git a/Lib/fontTools/varLib/instancer.py b/Lib/fontTools/varLib/instancer/__init__.py
index 52d62c99..9bd30f19 100644
--- a/Lib/fontTools/varLib/instancer.py
+++ b/Lib/fontTools/varLib/instancer/__init__.py
@@ -4,16 +4,17 @@ The module exports an `instantiateVariableFont` function and CLI that allow to
create full instances (i.e. static fonts) from variable fonts, as well as "partial"
variable fonts that only contain a subset of the original variation space.
-For example, if you wish to pin the width axis to a given location while keeping
-the rest of the axes, you can do:
+For example, if you wish to pin the width axis to a given location while also
+restricting the weight axis to 400..700 range, you can do:
-$ fonttools varLib.instancer ./NotoSans-VF.ttf wdth=85
+$ fonttools varLib.instancer ./NotoSans-VF.ttf wdth=85 wght=400:700
See `fonttools varLib.instancer --help` for more info on the CLI options.
The module's entry point is the `instantiateVariableFont` function, which takes
-a TTFont object and a dict specifying a location along either some or all the axes,
-and returns a new TTFont representing respectively a partial or a full instance.
+a TTFont object and a dict specifying either axis coodinates or (min, max) ranges,
+and returns a new TTFont representing either a partial VF, or full instance if all
+the VF axes were given an explicit coordinate.
E.g. here's how to pin the wght axis at a given location in a wght+wdth variable
font, keeping only the deltas associated with the wdth axis:
@@ -21,7 +22,7 @@ font, keeping only the deltas associated with the wdth axis:
| >>> from fontTools import ttLib
| >>> from fontTools.varLib import instancer
| >>> varfont = ttLib.TTFont("path/to/MyVariableFont.ttf")
-| >>> [a.axisTag for a in partial["fvar"].axes] # the varfont's current axes
+| >>> [a.axisTag for a in varfont["fvar"].axes] # the varfont's current axes
| ['wght', 'wdth']
| >>> partial = instancer.instantiateVariableFont(varfont, {"wght": 300})
| >>> [a.axisTag for a in partial["fvar"].axes] # axes left after pinning 'wght'
@@ -50,7 +51,7 @@ Note that, unlike varLib.mutator, when an axis is not mentioned in the input
location, the varLib.instancer will keep the axis and the corresponding deltas,
whereas mutator implicitly drops the axis at its default coordinate.
-The module currently supports only the first two "levels" of partial instancing,
+The module currently supports only the first three "levels" of partial instancing,
with the rest planned to be implemented in the future, namely:
L1) dropping one or more axes while leaving the default tables unmodified;
L2) dropping one or more axes while pinning them at non-default locations;
@@ -65,9 +66,12 @@ are supported, but support for CFF2 variable fonts will be added soon.
The discussion and implementation of these features are tracked at
https://github.com/fonttools/fonttools/issues/1537
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import floatToFixedToFloat, otRound
+from fontTools.misc.fixedTools import (
+ floatToFixedToFloat,
+ strToFixedToFloat,
+ otRound,
+ MAX_F2DOT14,
+)
from fontTools.varLib.models import supportScalar, normalizeValue, piecewiseLinearMap
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.TupleVariation import TupleVariation
@@ -80,9 +84,11 @@ from fontTools import subset # noqa: F401
from fontTools.varLib import builder
from fontTools.varLib.mvar import MVAR_ENTRIES
from fontTools.varLib.merger import MutatorMerger
+from fontTools.varLib.instancer import names
from contextlib import contextmanager
import collections
from copy import deepcopy
+from enum import IntEnum
import logging
from itertools import islice
import os
@@ -92,12 +98,50 @@ import re
log = logging.getLogger("fontTools.varLib.instancer")
-def instantiateTupleVariationStore(variations, location, origCoords=None, endPts=None):
- """Instantiate TupleVariation list at the given location.
+class AxisRange(collections.namedtuple("AxisRange", "minimum maximum")):
+ def __new__(cls, *args, **kwargs):
+ self = super().__new__(cls, *args, **kwargs)
+ if self.minimum > self.maximum:
+ raise ValueError(
+ f"Range minimum ({self.minimum:g}) must be <= maximum ({self.maximum:g})"
+ )
+ return self
+
+ def __repr__(self):
+ return f"{type(self).__name__}({self.minimum:g}, {self.maximum:g})"
+
+
+class NormalizedAxisRange(AxisRange):
+ def __new__(cls, *args, **kwargs):
+ self = super().__new__(cls, *args, **kwargs)
+ if self.minimum < -1.0 or self.maximum > 1.0:
+ raise ValueError("Axis range values must be normalized to -1..+1 range")
+ if self.minimum > 0:
+ raise ValueError(f"Expected axis range minimum <= 0; got {self.minimum}")
+ if self.maximum < 0:
+ raise ValueError(f"Expected axis range maximum >= 0; got {self.maximum}")
+ return self
+
+
+class OverlapMode(IntEnum):
+ KEEP_AND_DONT_SET_FLAGS = 0
+ KEEP_AND_SET_FLAGS = 1
+ REMOVE = 2
+
+
+def instantiateTupleVariationStore(
+ variations, axisLimits, origCoords=None, endPts=None
+):
+ """Instantiate TupleVariation list at the given location, or limit axes' min/max.
The 'variations' list of TupleVariation objects is modified in-place.
- The input location can describe either a full instance (all the axes are assigned an
- explicit coordinate) or partial (some of the axes are omitted).
+ The 'axisLimits' (dict) maps axis tags (str) to either a single coordinate along the
+ axis (float), or to minimum/maximum coordinates (NormalizedAxisRange).
+
+ A 'full' instance (i.e. static font) is produced when all the axes are pinned to
+ single coordinates; a 'partial' instance (i.e. a less variable font) is produced
+ when some of the axes are omitted, or restricted with a new range.
+
Tuples that do not participate are kept as they are. Those that have 0 influence
at the given location are removed from the variation store.
Those that are fully instantiated (i.e. all their axes are being pinned) are also
@@ -109,7 +153,8 @@ def instantiateTupleVariationStore(variations, location, origCoords=None, endPts
Args:
variations: List[TupleVariation] from either 'gvar' or 'cvar'.
- location: Dict[str, float]: axes coordinates for the full or partial instance.
+ axisLimits: Dict[str, Union[float, NormalizedAxisRange]]: axes' coordinates for
+ the full or partial instance, or ranges for restricting an axis' min/max.
origCoords: GlyphCoordinates: default instance's coordinates for computing 'gvar'
inferred points (cf. table__g_l_y_f.getCoordinatesAndControls).
endPts: List[int]: indices of contour end points, for inferring 'gvar' deltas.
@@ -117,7 +162,44 @@ def instantiateTupleVariationStore(variations, location, origCoords=None, endPts
Returns:
List[float]: the overall delta adjustment after applicable deltas were summed.
"""
- newVariations = collections.OrderedDict()
+ pinnedLocation, axisRanges = splitAxisLocationAndRanges(
+ axisLimits, rangeType=NormalizedAxisRange
+ )
+
+ newVariations = variations
+
+ if pinnedLocation:
+ newVariations = pinTupleVariationAxes(variations, pinnedLocation)
+
+ if axisRanges:
+ newVariations = limitTupleVariationAxisRanges(newVariations, axisRanges)
+
+ mergedVariations = collections.OrderedDict()
+ for var in newVariations:
+ # compute inferred deltas only for gvar ('origCoords' is None for cvar)
+ if origCoords is not None:
+ var.calcInferredDeltas(origCoords, endPts)
+
+ # merge TupleVariations with overlapping "tents"
+ axes = frozenset(var.axes.items())
+ if axes in mergedVariations:
+ mergedVariations[axes] += var
+ else:
+ mergedVariations[axes] = var
+
+ # drop TupleVariation if all axes have been pinned (var.axes.items() is empty);
+ # its deltas will be added to the default instance's coordinates
+ defaultVar = mergedVariations.pop(frozenset(), None)
+
+ for var in mergedVariations.values():
+ var.roundDeltas()
+ variations[:] = list(mergedVariations.values())
+
+ return defaultVar.coordinates if defaultVar is not None else []
+
+
+def pinTupleVariationAxes(variations, location):
+ newVariations = []
for var in variations:
# Compute the scalar support of the axes to be pinned at the desired location,
# excluding any axes that we are not pinning.
@@ -129,31 +211,119 @@ def instantiateTupleVariationStore(variations, location, origCoords=None, endPts
# no influence, drop the TupleVariation
continue
- # compute inferred deltas only for gvar ('origCoords' is None for cvar)
- if origCoords is not None:
- var.calcInferredDeltas(origCoords, endPts)
-
var.scaleDeltas(scalar)
+ newVariations.append(var)
+ return newVariations
- # merge TupleVariations with overlapping "tents"
- axes = tuple(var.axes.items())
- if axes in newVariations:
- newVariations[axes] += var
- else:
- newVariations[axes] = var
- # drop TupleVariation if all axes have been pinned (var.axes.items() is empty);
- # its deltas will be added to the default instance's coordinates
- defaultVar = newVariations.pop(tuple(), None)
+def limitTupleVariationAxisRanges(variations, axisRanges):
+ for axisTag, axisRange in sorted(axisRanges.items()):
+ newVariations = []
+ for var in variations:
+ newVariations.extend(limitTupleVariationAxisRange(var, axisTag, axisRange))
+ variations = newVariations
+ return variations
- for var in newVariations.values():
- var.roundDeltas()
- variations[:] = list(newVariations.values())
- return defaultVar.coordinates if defaultVar is not None else []
+def _negate(*values):
+ yield from (-1 * v for v in values)
+
+
+def limitTupleVariationAxisRange(var, axisTag, axisRange):
+ if not isinstance(axisRange, NormalizedAxisRange):
+ axisRange = NormalizedAxisRange(*axisRange)
+
+ # skip when current axis is missing (i.e. doesn't participate), or when the
+ # 'tent' isn't fully on either the negative or positive side
+ lower, peak, upper = var.axes.get(axisTag, (-1, 0, 1))
+ if peak == 0 or lower > peak or peak > upper or (lower < 0 and upper > 0):
+ return [var]
+
+ negative = lower < 0
+ if negative:
+ if axisRange.minimum == -1.0:
+ return [var]
+ elif axisRange.minimum == 0.0:
+ return []
+ else:
+ if axisRange.maximum == 1.0:
+ return [var]
+ elif axisRange.maximum == 0.0:
+ return []
+
+ limit = axisRange.minimum if negative else axisRange.maximum
+
+ # Rebase axis bounds onto the new limit, which then becomes the new -1.0 or +1.0.
+ # The results are always positive, because both dividend and divisor are either
+ # all positive or all negative.
+ newLower = lower / limit
+ newPeak = peak / limit
+ newUpper = upper / limit
+ # for negative TupleVariation, swap lower and upper to simplify procedure
+ if negative:
+ newLower, newUpper = newUpper, newLower
+
+ # special case when innermost bound == peak == limit
+ if newLower == newPeak == 1.0:
+ var.axes[axisTag] = (-1.0, -1.0, -1.0) if negative else (1.0, 1.0, 1.0)
+ return [var]
+
+ # case 1: the whole deltaset falls outside the new limit; we can drop it
+ elif newLower >= 1.0:
+ return []
+
+ # case 2: only the peak and outermost bound fall outside the new limit;
+ # we keep the deltaset, update peak and outermost bound and and scale deltas
+ # by the scalar value for the restricted axis at the new limit.
+ elif newPeak >= 1.0:
+ scalar = supportScalar({axisTag: limit}, {axisTag: (lower, peak, upper)})
+ var.scaleDeltas(scalar)
+ newPeak = 1.0
+ newUpper = 1.0
+ if negative:
+ newLower, newPeak, newUpper = _negate(newUpper, newPeak, newLower)
+ var.axes[axisTag] = (newLower, newPeak, newUpper)
+ return [var]
+
+ # case 3: peak falls inside but outermost limit still fits within F2Dot14 bounds;
+ # we keep deltas as is and only scale the axes bounds. Deltas beyond -1.0
+ # or +1.0 will never be applied as implementations must clamp to that range.
+ elif newUpper <= 2.0:
+ if negative:
+ newLower, newPeak, newUpper = _negate(newUpper, newPeak, newLower)
+ elif MAX_F2DOT14 < newUpper <= 2.0:
+ # we clamp +2.0 to the max F2Dot14 (~1.99994) for convenience
+ newUpper = MAX_F2DOT14
+ var.axes[axisTag] = (newLower, newPeak, newUpper)
+ return [var]
+
+ # case 4: new limit doesn't fit; we need to chop the deltaset into two 'tents',
+ # because the shape of a triangle with part of one side cut off cannot be
+ # represented as a triangle itself. It can be represented as sum of two triangles.
+ # NOTE: This increases the file size!
+ else:
+ # duplicate the tent, then adjust lower/peak/upper so that the outermost limit
+ # of the original tent is +/-2.0, whereas the new tent's starts as the old
+ # one peaks and maxes out at +/-1.0.
+ newVar = TupleVariation(var.axes, var.coordinates)
+ if negative:
+ var.axes[axisTag] = (-2.0, -1 * newPeak, -1 * newLower)
+ newVar.axes[axisTag] = (-1.0, -1.0, -1 * newPeak)
+ else:
+ var.axes[axisTag] = (newLower, newPeak, MAX_F2DOT14)
+ newVar.axes[axisTag] = (newPeak, 1.0, 1.0)
+ # the new tent's deltas are scaled by the difference between the scalar value
+ # for the old tent at the desired limit...
+ scalar1 = supportScalar({axisTag: limit}, {axisTag: (lower, peak, upper)})
+ # ... and the scalar value for the clamped tent (with outer limit +/-2.0),
+ # which can be simplified like this:
+ scalar2 = 1 / (2 - newPeak)
+ newVar.scaleDeltas(scalar1 - scalar2)
+ return [var, newVar]
-def instantiateGvarGlyph(varfont, glyphname, location, optimize=True):
+
+def instantiateGvarGlyph(varfont, glyphname, axisLimits, optimize=True):
glyf = varfont["glyf"]
coordinates, ctrl = glyf.getCoordinatesAndControls(glyphname, varfont)
endPts = ctrl.endPts
@@ -165,7 +335,7 @@ def instantiateGvarGlyph(varfont, glyphname, location, optimize=True):
if tupleVarStore:
defaultDeltas = instantiateTupleVariationStore(
- tupleVarStore, location, coordinates, endPts
+ tupleVarStore, axisLimits, coordinates, endPts
)
if defaultDeltas:
@@ -193,7 +363,7 @@ def instantiateGvarGlyph(varfont, glyphname, location, optimize=True):
var.optimize(coordinates, endPts, isComposite)
-def instantiateGvar(varfont, location, optimize=True):
+def instantiateGvar(varfont, axisLimits, optimize=True):
log.info("Instantiating glyf/gvar tables")
gvar = varfont["gvar"]
@@ -212,7 +382,7 @@ def instantiateGvar(varfont, location, optimize=True):
),
)
for glyphname in glyphnames:
- instantiateGvarGlyph(varfont, glyphname, location, optimize=optimize)
+ instantiateGvarGlyph(varfont, glyphname, axisLimits, optimize=optimize)
if not gvar.variations:
del varfont["gvar"]
@@ -224,12 +394,12 @@ def setCvarDeltas(cvt, deltas):
cvt[i] += otRound(delta)
-def instantiateCvar(varfont, location):
+def instantiateCvar(varfont, axisLimits):
log.info("Instantiating cvt/cvar tables")
cvar = varfont["cvar"]
- defaultDeltas = instantiateTupleVariationStore(cvar.variations, location)
+ defaultDeltas = instantiateTupleVariationStore(cvar.variations, axisLimits)
if defaultDeltas:
setCvarDeltas(varfont["cvt "], defaultDeltas)
@@ -255,13 +425,13 @@ def setMvarDeltas(varfont, deltas):
)
-def instantiateMVAR(varfont, location):
+def instantiateMVAR(varfont, axisLimits):
log.info("Instantiating MVAR table")
mvar = varfont["MVAR"].table
fvarAxes = varfont["fvar"].axes
varStore = mvar.VarStore
- defaultDeltas = instantiateItemVariationStore(varStore, fvarAxes, location)
+ defaultDeltas = instantiateItemVariationStore(varStore, fvarAxes, axisLimits)
setMvarDeltas(varfont, defaultDeltas)
if varStore.VarRegionList.Region:
@@ -279,12 +449,14 @@ def _remapVarIdxMap(table, attrName, varIndexMapping, glyphOrder):
# TODO(anthrotype) Add support for HVAR/VVAR in CFF2
-def _instantiateVHVAR(varfont, location, tableFields):
+def _instantiateVHVAR(varfont, axisLimits, tableFields):
tableTag = tableFields.tableTag
fvarAxes = varfont["fvar"].axes
# Deltas from gvar table have already been applied to the hmtx/vmtx. For full
# instances (i.e. all axes pinned), we can simply drop HVAR/VVAR and return
- if set(location).issuperset(axis.axisTag for axis in fvarAxes):
+ if set(
+ axisTag for axisTag, value in axisLimits.items() if not isinstance(value, tuple)
+ ).issuperset(axis.axisTag for axis in fvarAxes):
log.info("Dropping %s table", tableTag)
del varfont[tableTag]
return
@@ -293,7 +465,7 @@ def _instantiateVHVAR(varfont, location, tableFields):
vhvar = varfont[tableTag].table
varStore = vhvar.VarStore
# since deltas were already applied, the return value here is ignored
- instantiateItemVariationStore(varStore, fvarAxes, location)
+ instantiateItemVariationStore(varStore, fvarAxes, axisLimits)
if varStore.VarRegionList.Region:
# Only re-optimize VarStore if the HVAR/VVAR already uses indirect AdvWidthMap
@@ -311,16 +483,14 @@ def _instantiateVHVAR(varfont, location, tableFields):
_remapVarIdxMap(
vhvar, tableFields.vOrigMapping, varIndexMapping, glyphOrder
)
- else:
- del varfont[tableTag]
-def instantiateHVAR(varfont, location):
- return _instantiateVHVAR(varfont, location, varLib.HVAR_FIELDS)
+def instantiateHVAR(varfont, axisLimits):
+ return _instantiateVHVAR(varfont, axisLimits, varLib.HVAR_FIELDS)
-def instantiateVVAR(varfont, location):
- return _instantiateVHVAR(varfont, location, varLib.VVAR_FIELDS)
+def instantiateVVAR(varfont, axisLimits):
+ return _instantiateVHVAR(varfont, axisLimits, varLib.VVAR_FIELDS)
class _TupleVarStoreAdapter(object):
@@ -347,30 +517,47 @@ class _TupleVarStoreAdapter(object):
itemCounts.append(varData.ItemCount)
return cls(regions, axisOrder, tupleVarData, itemCounts)
- def dropAxes(self, axes):
- prunedRegions = (
- frozenset(
- (axisTag, support)
- for axisTag, support in region.items()
- if axisTag not in axes
+ def rebuildRegions(self):
+ # Collect the set of all unique region axes from the current TupleVariations.
+ # We use an OrderedDict to de-duplicate regions while keeping the order.
+ uniqueRegions = collections.OrderedDict.fromkeys(
+ (
+ frozenset(var.axes.items())
+ for variations in self.tupleVarData
+ for var in variations
)
- for region in self.regions
)
- # dedup regions while keeping original order
- uniqueRegions = collections.OrderedDict.fromkeys(prunedRegions)
- self.regions = [dict(items) for items in uniqueRegions if items]
- self.axisOrder = [axisTag for axisTag in self.axisOrder if axisTag not in axes]
-
- def instantiate(self, location):
+ # Maintain the original order for the regions that pre-existed, appending
+ # the new regions at the end of the region list.
+ newRegions = []
+ for region in self.regions:
+ regionAxes = frozenset(region.items())
+ if regionAxes in uniqueRegions:
+ newRegions.append(region)
+ del uniqueRegions[regionAxes]
+ if uniqueRegions:
+ newRegions.extend(dict(region) for region in uniqueRegions)
+ self.regions = newRegions
+
+ def instantiate(self, axisLimits):
defaultDeltaArray = []
for variations, itemCount in zip(self.tupleVarData, self.itemCounts):
- defaultDeltas = instantiateTupleVariationStore(variations, location)
+ defaultDeltas = instantiateTupleVariationStore(variations, axisLimits)
if not defaultDeltas:
defaultDeltas = [0] * itemCount
defaultDeltaArray.append(defaultDeltas)
- # remove pinned axes from all the regions
- self.dropAxes(location.keys())
+ # rebuild regions whose axes were dropped or limited
+ self.rebuildRegions()
+
+ pinnedAxes = {
+ axisTag
+ for axisTag, value in axisLimits.items()
+ if not isinstance(value, tuple)
+ }
+ self.axisOrder = [
+ axisTag for axisTag in self.axisOrder if axisTag not in pinnedAxes
+ ]
return defaultDeltaArray
@@ -398,11 +585,12 @@ class _TupleVarStoreAdapter(object):
return itemVarStore
-def instantiateItemVariationStore(itemVarStore, fvarAxes, location):
- """ Compute deltas at partial location, and update varStore in-place.
+def instantiateItemVariationStore(itemVarStore, fvarAxes, axisLimits):
+ """Compute deltas at partial location, and update varStore in-place.
- Remove regions in which all axes were instanced, and scale the deltas of
- the remaining regions where only some of the axes were instanced.
+ Remove regions in which all axes were instanced, or fall outside the new axis
+ limits. Scale the deltas of the remaining regions where only some of the axes
+ were instanced.
The number of VarData subtables, and the number of items within each, are
not modified, in order to keep the existing VariationIndex valid.
@@ -411,15 +599,16 @@ def instantiateItemVariationStore(itemVarStore, fvarAxes, location):
Args:
varStore: An otTables.VarStore object (Item Variation Store)
fvarAxes: list of fvar's Axis objects
- location: Dict[str, float] mapping axis tags to normalized axis coordinates.
- May not specify coordinates for all the fvar axes.
+ axisLimits: Dict[str, float] mapping axis tags to normalized axis coordinates
+ (float) or ranges for restricting an axis' min/max (NormalizedAxisRange).
+ May not specify coordinates/ranges for all the fvar axes.
Returns:
defaultDeltas: to be added to the default instance, of type dict of floats
keyed by VariationIndex compound values: i.e. (outer << 16) + inner.
"""
tupleVarStore = _TupleVarStoreAdapter.fromItemVarStore(itemVarStore, fvarAxes)
- defaultDeltaArray = tupleVarStore.instantiate(location)
+ defaultDeltaArray = tupleVarStore.instantiate(axisLimits)
newItemVarStore = tupleVarStore.asItemVarStore()
itemVarStore.VarRegionList = newItemVarStore.VarRegionList
@@ -434,7 +623,7 @@ def instantiateItemVariationStore(itemVarStore, fvarAxes, location):
return defaultDeltas
-def instantiateOTL(varfont, location):
+def instantiateOTL(varfont, axisLimits):
# TODO(anthrotype) Support partial instancing of JSTF and BASE tables
if (
@@ -454,7 +643,7 @@ def instantiateOTL(varfont, location):
varStore = gdef.VarStore
fvarAxes = varfont["fvar"].axes
- defaultDeltas = instantiateItemVariationStore(varStore, fvarAxes, location)
+ defaultDeltas = instantiateItemVariationStore(varStore, fvarAxes, axisLimits)
# When VF are built, big lookups may overflow and be broken into multiple
# subtables. MutatorMerger (which inherits from AligningMerger) reattaches
@@ -493,15 +682,15 @@ def instantiateOTL(varfont, location):
del varfont["GDEF"]
-def instantiateFeatureVariations(varfont, location):
+def instantiateFeatureVariations(varfont, axisLimits):
for tableTag in ("GPOS", "GSUB"):
- if tableTag not in varfont or not hasattr(
- varfont[tableTag].table, "FeatureVariations"
+ if tableTag not in varfont or not getattr(
+ varfont[tableTag].table, "FeatureVariations", None
):
continue
log.info("Instantiating FeatureVariations of %s table", tableTag)
_instantiateFeatureVariations(
- varfont[tableTag].table, varfont["fvar"].axes, location
+ varfont[tableTag].table, varfont["fvar"].axes, axisLimits
)
# remove unreferenced lookups
varfont[tableTag].prune_lookups()
@@ -529,10 +718,44 @@ def _featureVariationRecordIsUnique(rec, seen):
return True
+def _limitFeatureVariationConditionRange(condition, axisRange):
+ minValue = condition.FilterRangeMinValue
+ maxValue = condition.FilterRangeMaxValue
+
+ if (
+ minValue > maxValue
+ or minValue > axisRange.maximum
+ or maxValue < axisRange.minimum
+ ):
+ # condition invalid or out of range
+ return
+
+ values = [minValue, maxValue]
+ for i, value in enumerate(values):
+ if value < 0:
+ if axisRange.minimum == 0:
+ newValue = 0
+ else:
+ newValue = value / abs(axisRange.minimum)
+ if newValue <= -1.0:
+ newValue = -1.0
+ elif value > 0:
+ if axisRange.maximum == 0:
+ newValue = 0
+ else:
+ newValue = value / axisRange.maximum
+ if newValue >= 1.0:
+ newValue = 1.0
+ else:
+ newValue = 0
+ values[i] = newValue
+
+ return AxisRange(*values)
+
+
def _instantiateFeatureVariationRecord(
record, recIdx, location, fvarAxes, axisIndexMap
):
- shouldKeep = False
applies = True
newConditions = []
for i, condition in enumerate(record.ConditionSet.ConditionTable):
@@ -564,11 +787,48 @@ def _instantiateFeatureVariationRecord(
if newConditions:
record.ConditionSet.ConditionTable = newConditions
shouldKeep = True
+ else:
+ shouldKeep = False
return applies, shouldKeep
-def _instantiateFeatureVariations(table, fvarAxes, location):
+def _limitFeatureVariationRecord(record, axisRanges, fvarAxes):
+ newConditions = []
+ for i, condition in enumerate(record.ConditionSet.ConditionTable):
+ if condition.Format == 1:
+ axisIdx = condition.AxisIndex
+ axisTag = fvarAxes[axisIdx].axisTag
+ if axisTag in axisRanges:
+ axisRange = axisRanges[axisTag]
+ newRange = _limitFeatureVariationConditionRange(condition, axisRange)
+ if newRange:
+ # keep condition with updated limits and remapped axis index
+ condition.FilterRangeMinValue = newRange.minimum
+ condition.FilterRangeMaxValue = newRange.maximum
+ newConditions.append(condition)
+ else:
+ # condition out of range, remove entire record
+ newConditions = None
+ break
+ else:
+ newConditions.append(condition)
+ else:
+ newConditions.append(condition)
+
+ if newConditions:
+ record.ConditionSet.ConditionTable = newConditions
+ shouldKeep = True
+ else:
+ shouldKeep = False
+
+ return shouldKeep
+
+
+def _instantiateFeatureVariations(table, fvarAxes, axisLimits):
+ location, axisRanges = splitAxisLocationAndRanges(
+ axisLimits, rangeType=NormalizedAxisRange
+ )
pinnedAxes = set(location.keys())
axisOrder = [axis.axisTag for axis in fvarAxes if axis.axisTag not in pinnedAxes]
axisIndexMap = {axisTag: axisOrder.index(axisTag) for axisTag in axisOrder}
@@ -582,8 +842,10 @@ def _instantiateFeatureVariations(table, fvarAxes, location):
record, i, location, fvarAxes, axisIndexMap
)
if shouldKeep:
- if _featureVariationRecordIsUnique(record, uniqueRecords):
- newRecords.append(record)
+ shouldKeep = _limitFeatureVariationRecord(record, axisRanges, fvarAxes)
+
+ if shouldKeep and _featureVariationRecordIsUnique(record, uniqueRecords):
+ newRecords.append(record)
if applies and not featureVariationApplied:
assert record.FeatureTableSubstitution.Version == 0x00010000
@@ -599,23 +861,111 @@ def _instantiateFeatureVariations(table, fvarAxes, location):
del table.FeatureVariations
-def instantiateAvar(varfont, location):
+def _isValidAvarSegmentMap(axisTag, segmentMap):
+ if not segmentMap:
+ return True
+ if not {(-1.0, -1.0), (0, 0), (1.0, 1.0)}.issubset(segmentMap.items()):
+ log.warning(
+ f"Invalid avar SegmentMap record for axis '{axisTag}': does not "
+ "include all required value maps {-1.0: -1.0, 0: 0, 1.0: 1.0}"
+ )
+ return False
+ previousValue = None
+ for fromCoord, toCoord in sorted(segmentMap.items()):
+ if previousValue is not None and previousValue > toCoord:
+ log.warning(
+ f"Invalid avar AxisValueMap({fromCoord}, {toCoord}) record "
+ f"for axis '{axisTag}': the toCoordinate value must be >= to "
+ f"the toCoordinate value of the preceding record ({previousValue})."
+ )
+ return False
+ previousValue = toCoord
+ return True
+
+
+def instantiateAvar(varfont, axisLimits):
+ # 'axisLimits' dict must contain user-space (non-normalized) coordinates.
+
+ location, axisRanges = splitAxisLocationAndRanges(axisLimits)
+
segments = varfont["avar"].segments
# drop table if we instantiate all the axes
- if set(location).issuperset(segments):
+ pinnedAxes = set(location.keys())
+ if pinnedAxes.issuperset(segments):
log.info("Dropping avar table")
del varfont["avar"]
return
log.info("Instantiating avar table")
- for axis in location:
+ for axis in pinnedAxes:
if axis in segments:
del segments[axis]
+ # First compute the default normalization for axisRanges coordinates: i.e.
+ # min = -1.0, default = 0, max = +1.0, and in between values interpolated linearly,
+ # without using the avar table's mappings.
+ # Then, for each SegmentMap, if we are restricting its axis, compute the new
+ # mappings by dividing the key/value pairs by the desired new min/max values,
+ # dropping any mappings that fall outside the restricted range.
+ # The keys ('fromCoord') are specified in default normalized coordinate space,
+ # whereas the values ('toCoord') are "mapped forward" using the SegmentMap.
+ normalizedRanges = normalizeAxisLimits(varfont, axisRanges, usingAvar=False)
+ newSegments = {}
+ for axisTag, mapping in segments.items():
+ if not _isValidAvarSegmentMap(axisTag, mapping):
+ continue
+ if mapping and axisTag in normalizedRanges:
+ axisRange = normalizedRanges[axisTag]
+ mappedMin = floatToFixedToFloat(
+ piecewiseLinearMap(axisRange.minimum, mapping), 14
+ )
+ mappedMax = floatToFixedToFloat(
+ piecewiseLinearMap(axisRange.maximum, mapping), 14
+ )
+ newMapping = {}
+ for fromCoord, toCoord in mapping.items():
+ if fromCoord < 0:
+ if axisRange.minimum == 0 or fromCoord < axisRange.minimum:
+ continue
+ else:
+ fromCoord /= abs(axisRange.minimum)
+ elif fromCoord > 0:
+ if axisRange.maximum == 0 or fromCoord > axisRange.maximum:
+ continue
+ else:
+ fromCoord /= axisRange.maximum
+ if toCoord < 0:
+ assert mappedMin != 0
+ assert toCoord >= mappedMin
+ toCoord /= abs(mappedMin)
+ elif toCoord > 0:
+ assert mappedMax != 0
+ assert toCoord <= mappedMax
+ toCoord /= mappedMax
+ fromCoord = floatToFixedToFloat(fromCoord, 14)
+ toCoord = floatToFixedToFloat(toCoord, 14)
+ newMapping[fromCoord] = toCoord
+ newMapping.update({-1.0: -1.0, 1.0: 1.0})
+ newSegments[axisTag] = newMapping
+ else:
+ newSegments[axisTag] = mapping
+ varfont["avar"].segments = newSegments
+
+
+def isInstanceWithinAxisRanges(location, axisRanges):
+ for axisTag, coord in location.items():
+ if axisTag in axisRanges:
+ axisRange = axisRanges[axisTag]
+ if coord < axisRange.minimum or coord > axisRange.maximum:
+ return False
+ return True
-def instantiateFvar(varfont, location):
- # 'location' dict must contain user-space (non-normalized) coordinates
+
+def instantiateFvar(varfont, axisLimits):
+ # 'axisLimits' dict must contain user-space (non-normalized) coordinates
+
+ location, axisRanges = splitAxisLocationAndRanges(axisLimits, rangeType=AxisRange)
fvar = varfont["fvar"]
@@ -627,116 +977,86 @@ def instantiateFvar(varfont, location):
log.info("Instantiating fvar table")
- fvar.axes = [axis for axis in fvar.axes if axis.axisTag not in location]
+ axes = []
+ for axis in fvar.axes:
+ axisTag = axis.axisTag
+ if axisTag in location:
+ continue
+ if axisTag in axisRanges:
+ axis.minValue, axis.maxValue = axisRanges[axisTag]
+ axes.append(axis)
+ fvar.axes = axes
# only keep NamedInstances whose coordinates == pinned axis location
instances = []
for instance in fvar.instances:
if any(instance.coordinates[axis] != value for axis, value in location.items()):
continue
- for axis in location:
- del instance.coordinates[axis]
+ for axisTag in location:
+ del instance.coordinates[axisTag]
+ if not isInstanceWithinAxisRanges(instance.coordinates, axisRanges):
+ continue
instances.append(instance)
fvar.instances = instances
-def instantiateSTAT(varfont, location):
- pinnedAxes = set(location.keys())
+def instantiateSTAT(varfont, axisLimits):
+ # 'axisLimits' dict must contain user-space (non-normalized) coordinates
stat = varfont["STAT"].table
- if not stat.DesignAxisRecord:
- return # skip empty STAT table
-
- designAxes = stat.DesignAxisRecord.Axis
- pinnedAxisIndices = {
- i for i, axis in enumerate(designAxes) if axis.AxisTag in pinnedAxes
- }
-
- if len(pinnedAxisIndices) == len(designAxes):
- log.info("Dropping STAT table")
- del varfont["STAT"]
- return
+ if not stat.DesignAxisRecord or not (
+ stat.AxisValueArray and stat.AxisValueArray.AxisValue
+ ):
+ return # STAT table empty, nothing to do
log.info("Instantiating STAT table")
+ newAxisValueTables = axisValuesFromAxisLimits(stat, axisLimits)
+ stat.AxisValueArray.AxisValue = newAxisValueTables
+ stat.AxisValueCount = len(stat.AxisValueArray.AxisValue)
- # only keep DesignAxis that were not instanced, and build a mapping from old
- # to new axis indices
- newDesignAxes = []
- axisIndexMap = {}
- for i, axis in enumerate(designAxes):
- if i not in pinnedAxisIndices:
- axisIndexMap[i] = len(newDesignAxes)
- newDesignAxes.append(axis)
-
- if stat.AxisValueArray and stat.AxisValueArray.AxisValue:
- # drop all AxisValue tables that reference any of the pinned axes
- newAxisValueTables = []
- for axisValueTable in stat.AxisValueArray.AxisValue:
- if axisValueTable.Format in (1, 2, 3):
- if axisValueTable.AxisIndex in pinnedAxisIndices:
- continue
- axisValueTable.AxisIndex = axisIndexMap[axisValueTable.AxisIndex]
- newAxisValueTables.append(axisValueTable)
- elif axisValueTable.Format == 4:
- if any(
- rec.AxisIndex in pinnedAxisIndices
- for rec in axisValueTable.AxisValueRecord
- ):
- continue
- for rec in axisValueTable.AxisValueRecord:
- rec.AxisIndex = axisIndexMap[rec.AxisIndex]
- newAxisValueTables.append(axisValueTable)
- else:
- raise NotImplementedError(axisValueTable.Format)
- stat.AxisValueArray.AxisValue = newAxisValueTables
- stat.AxisValueCount = len(stat.AxisValueArray.AxisValue)
- stat.DesignAxisRecord.Axis[:] = newDesignAxes
- stat.DesignAxisCount = len(stat.DesignAxisRecord.Axis)
+def axisValuesFromAxisLimits(stat, axisLimits):
+ location, axisRanges = splitAxisLocationAndRanges(axisLimits, rangeType=AxisRange)
+ def isAxisValueOutsideLimits(axisTag, axisValue):
+ if axisTag in location and axisValue != location[axisTag]:
+ return True
+ elif axisTag in axisRanges:
+ axisRange = axisRanges[axisTag]
+ if axisValue < axisRange.minimum or axisValue > axisRange.maximum:
+ return True
+ return False
-def getVariationNameIDs(varfont):
- used = []
- if "fvar" in varfont:
- fvar = varfont["fvar"]
- for axis in fvar.axes:
- used.append(axis.axisNameID)
- for instance in fvar.instances:
- used.append(instance.subfamilyNameID)
- if instance.postscriptNameID != 0xFFFF:
- used.append(instance.postscriptNameID)
- if "STAT" in varfont:
- stat = varfont["STAT"].table
- for axis in stat.DesignAxisRecord.Axis if stat.DesignAxisRecord else ():
- used.append(axis.AxisNameID)
- for value in stat.AxisValueArray.AxisValue if stat.AxisValueArray else ():
- used.append(value.ValueNameID)
- # nameIDs <= 255 are reserved by OT spec so we don't touch them
- return {nameID for nameID in used if nameID > 255}
-
-
-@contextmanager
-def pruningUnusedNames(varfont):
- origNameIDs = getVariationNameIDs(varfont)
-
- yield
-
- log.info("Pruning name table")
- exclude = origNameIDs - getVariationNameIDs(varfont)
- varfont["name"].names[:] = [
- record for record in varfont["name"].names if record.nameID not in exclude
- ]
- if "ltag" in varfont:
- # Drop the whole 'ltag' table if all the language-dependent Unicode name
- # records that reference it have been dropped.
- # TODO: Only prune unused ltag tags, renumerating langIDs accordingly.
- # Note ltag can also be used by feat or morx tables, so check those too.
- if not any(
- record
- for record in varfont["name"].names
- if record.platformID == 0 and record.langID != 0xFFFF
- ):
- del varfont["ltag"]
+ # only keep AxisValues whose axis is not pinned nor restricted, or is pinned at the
+ # exact (nominal) value, or is restricted but the value is within the new range
+ designAxes = stat.DesignAxisRecord.Axis
+ newAxisValueTables = []
+ for axisValueTable in stat.AxisValueArray.AxisValue:
+ axisValueFormat = axisValueTable.Format
+ if axisValueFormat in (1, 2, 3):
+ axisTag = designAxes[axisValueTable.AxisIndex].AxisTag
+ if axisValueFormat == 2:
+ axisValue = axisValueTable.NominalValue
+ else:
+ axisValue = axisValueTable.Value
+ if isAxisValueOutsideLimits(axisTag, axisValue):
+ continue
+ elif axisValueFormat == 4:
+ # drop 'non-analytic' AxisValue if _any_ AxisValueRecord doesn't match
+ # the pinned location or is outside range
+ dropAxisValueTable = False
+ for rec in axisValueTable.AxisValueRecord:
+ axisTag = designAxes[rec.AxisIndex].AxisTag
+ axisValue = rec.Value
+ if isAxisValueOutsideLimits(axisTag, axisValue):
+ dropAxisValueTable = True
+ break
+ if dropAxisValueTable:
+ continue
+ else:
+ log.warning("Unknown AxisValue table format (%s); ignored", axisValueFormat)
+ newAxisValueTables.append(axisValueTable)
+ return newAxisValueTables
def setMacOverlapFlags(glyfTable):
@@ -760,7 +1080,7 @@ def normalize(value, triple, avarMapping):
return floatToFixedToFloat(value, 14)
-def normalizeAxisLimits(varfont, axisLimits):
+def normalizeAxisLimits(varfont, axisLimits, usingAvar=True):
fvar = varfont["fvar"]
badLimits = set(axisLimits.keys()).difference(a.axisTag for a in fvar.axes)
if badLimits:
@@ -773,15 +1093,26 @@ def normalizeAxisLimits(varfont, axisLimits):
}
avarSegments = {}
- if "avar" in varfont:
+ if usingAvar and "avar" in varfont:
avarSegments = varfont["avar"].segments
+
+ for axis_tag, (_, default, _) in axes.items():
+ value = axisLimits[axis_tag]
+ if isinstance(value, tuple):
+ minV, maxV = value
+ if minV > default or maxV < default:
+ raise NotImplementedError(
+ f"Unsupported range {axis_tag}={minV:g}:{maxV:g}; "
+ f"can't change default position ({axis_tag}={default:g})"
+ )
+
normalizedLimits = {}
for axis_tag, triple in axes.items():
avarMapping = avarSegments.get(axis_tag, None)
value = axisLimits[axis_tag]
if isinstance(value, tuple):
- normalizedLimits[axis_tag] = tuple(
- normalize(v, triple, avarMapping) for v in axisLimits[axis_tag]
+ normalizedLimits[axis_tag] = NormalizedAxisRange(
+ *(normalize(v, triple, avarMapping) for v in value)
)
else:
normalizedLimits[axis_tag] = normalize(value, triple, avarMapping)
@@ -811,9 +1142,14 @@ def populateAxisDefaults(varfont, axisLimits):
def instantiateVariableFont(
- varfont, axisLimits, inplace=False, optimize=True, overlap=True
+ varfont,
+ axisLimits,
+ inplace=False,
+ optimize=True,
+ overlap=OverlapMode.KEEP_AND_SET_FLAGS,
+ updateFontNames=False,
):
- """ Instantiate variable font, either fully or partially.
+ """Instantiate variable font, either fully or partially.
Depending on whether the `axisLimits` dictionary references all or some of the
input varfont's axes, the output font will either be a full instance (static
@@ -834,17 +1170,26 @@ def instantiateVariableFont(
remaining 'gvar' table's deltas. Possibly faster, and might work around
rendering issues in some buggy environments, at the cost of a slightly
larger file size.
- overlap (bool): variable fonts usually contain overlapping contours, and some
- font rendering engines on Apple platforms require that the `OVERLAP_SIMPLE`
- and `OVERLAP_COMPOUND` flags in the 'glyf' table be set to force rendering
- using a non-zero fill rule. Thus we always set these flags on all glyphs
- to maximise cross-compatibility of the generated instance. You can disable
- this by setting `overalap` to False.
+ overlap (OverlapMode): variable fonts usually contain overlapping contours, and
+ some font rendering engines on Apple platforms require that the
+ `OVERLAP_SIMPLE` and `OVERLAP_COMPOUND` flags in the 'glyf' table be set to
+ force rendering using a non-zero fill rule. Thus we always set these flags
+ on all glyphs to maximise cross-compatibility of the generated instance.
+ You can disable this by passing OverlapMode.KEEP_AND_DONT_SET_FLAGS.
+ If you want to remove the overlaps altogether and merge overlapping
+ contours and components, you can pass OverlapMode.REMOVE. Note that this
+ requires the skia-pathops package (available to pip install).
+ The overlap parameter only has effect when generating full static instances.
+ updateFontNames (bool): if True, update the instantiated font's name table using
+ the Axis Value Tables from the STAT table. The name table will be updated so
+ it conforms to the R/I/B/BI model. If the STAT table is missing or
+ an Axis Value table is missing for a given axis coordinate, a ValueError will
+ be raised.
"""
- sanityCheckVariableTables(varfont)
+ # 'overlap' used to be bool and is now enum; for backward compat keep accepting bool
+ overlap = OverlapMode(int(overlap))
- if not inplace:
- varfont = deepcopy(varfont)
+ sanityCheckVariableTables(varfont)
axisLimits = populateAxisDefaults(varfont, axisLimits)
@@ -852,9 +1197,12 @@ def instantiateVariableFont(
log.info("Normalized limits: %s", normalizedLimits)
- # TODO Remove this check once ranges are supported
- if any(isinstance(v, tuple) for v in axisLimits.values()):
- raise NotImplementedError("Axes range limits are not supported yet")
+ if not inplace:
+ varfont = deepcopy(varfont)
+
+ if updateFontNames:
+ log.info("Updating name table")
+ names.updateNameTable(varfont, axisLimits)
if "gvar" in varfont:
instantiateGvar(varfont, normalizedLimits, optimize=optimize)
@@ -876,17 +1224,23 @@ def instantiateVariableFont(
instantiateFeatureVariations(varfont, normalizedLimits)
if "avar" in varfont:
- instantiateAvar(varfont, normalizedLimits)
+ instantiateAvar(varfont, axisLimits)
- with pruningUnusedNames(varfont):
+ with names.pruningUnusedNames(varfont):
if "STAT" in varfont:
instantiateSTAT(varfont, axisLimits)
instantiateFvar(varfont, axisLimits)
if "fvar" not in varfont:
- if "glyf" in varfont and overlap:
- setMacOverlapFlags(varfont["glyf"])
+ if "glyf" in varfont:
+ if overlap == OverlapMode.KEEP_AND_SET_FLAGS:
+ setMacOverlapFlags(varfont["glyf"])
+ elif overlap == OverlapMode.REMOVE:
+ from fontTools.ttLib.removeOverlaps import removeOverlaps
+
+ log.info("Removing overlaps from glyf table")
+ removeOverlaps(varfont)
varLib.set_default_weight_width_slant(
varfont,
@@ -900,6 +1254,23 @@ def instantiateVariableFont(
return varfont
+def splitAxisLocationAndRanges(axisLimits, rangeType=AxisRange):
+ location, axisRanges = {}, {}
+ for axisTag, value in axisLimits.items():
+ if isinstance(value, rangeType):
+ axisRanges[axisTag] = value
+ elif isinstance(value, (int, float)):
+ location[axisTag] = value
+ elif isinstance(value, tuple):
+ axisRanges[axisTag] = rangeType(*value)
+ else:
+ raise TypeError(
+ f"Expected number or {rangeType.__name__}, "
+ f"got {type(value).__name__}: {value!r}"
+ )
+ return location, axisRanges
+
+
def parseLimits(limits):
result = {}
for limitString in limits:
@@ -910,12 +1281,12 @@ def parseLimits(limits):
if match.group(2): # 'drop'
lbound = None
else:
- lbound = float(match.group(3))
+ lbound = strToFixedToFloat(match.group(3), precisionBits=16)
ubound = lbound
if match.group(4):
- ubound = float(match.group(4))
+ ubound = strToFixedToFloat(match.group(4), precisionBits=16)
if lbound != ubound:
- result[tag] = (lbound, ubound)
+ result[tag] = AxisRange(lbound, ubound)
else:
result[tag] = lbound
return result
@@ -944,7 +1315,7 @@ def parseArgs(args):
"locargs",
metavar="AXIS=LOC",
nargs="*",
- help="List of space separated locations. A location consist in "
+ help="List of space separated locations. A location consists of "
"the tag of a variation axis, followed by '=' and one of number, "
"number:number or the literal string 'drop'. "
"E.g.: wdth=100 or wght=75.0:125.0 or wght=drop",
@@ -969,6 +1340,19 @@ def parseArgs(args):
help="Don't set OVERLAP_SIMPLE/OVERLAP_COMPOUND glyf flags (only applicable "
"when generating a full instance)",
)
+ parser.add_argument(
+ "--remove-overlaps",
+ dest="remove_overlaps",
+ action="store_true",
+ help="Merge overlapping contours and components (only applicable "
+ "when generating a full instance). Requires skia-pathops",
+ )
+ parser.add_argument(
+ "--update-name-table",
+ action="store_true",
+ help="Update the instantiated font's `name` table. Input font must have "
+ "a STAT table with Axis Value Tables",
+ )
loggingGroup = parser.add_mutually_exclusive_group(required=False)
loggingGroup.add_argument(
"-v", "--verbose", action="store_true", help="Run more verbosely."
@@ -978,6 +1362,11 @@ def parseArgs(args):
)
options = parser.parse_args(args)
+ if options.remove_overlaps:
+ options.overlap = OverlapMode.REMOVE
+ else:
+ options.overlap = OverlapMode(int(options.overlap))
+
infile = options.input
if not os.path.isfile(infile):
parser.error("No such file '{}'".format(infile))
@@ -989,7 +1378,7 @@ def parseArgs(args):
try:
axisLimits = parseLimits(options.locargs)
except ValueError as e:
- parser.error(e)
+ parser.error(str(e))
if len(axisLimits) != len(options.locargs):
parser.error("Specified multiple limits for the same axis")
@@ -998,6 +1387,7 @@ def parseArgs(args):
def main(args=None):
+ """Partially instantiate a variable font."""
infile, axisLimits, options = parseArgs(args)
log.info("Restricting axes: %s", axisLimits)
@@ -1014,6 +1404,7 @@ def main(args=None):
inplace=True,
optimize=options.optimize,
overlap=options.overlap,
+ updateFontNames=options.update_name_table,
)
outfile = (
@@ -1029,9 +1420,3 @@ def main(args=None):
outfile,
)
varfont.save(outfile)
-
-
-if __name__ == "__main__":
- import sys
-
- sys.exit(main())
diff --git a/Lib/fontTools/varLib/instancer/__main__.py b/Lib/fontTools/varLib/instancer/__main__.py
new file mode 100644
index 00000000..64ffff2b
--- /dev/null
+++ b/Lib/fontTools/varLib/instancer/__main__.py
@@ -0,0 +1,5 @@
+import sys
+from fontTools.varLib.instancer import main
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/Lib/fontTools/varLib/instancer/names.py b/Lib/fontTools/varLib/instancer/names.py
new file mode 100644
index 00000000..cfe12a94
--- /dev/null
+++ b/Lib/fontTools/varLib/instancer/names.py
@@ -0,0 +1,379 @@
+"""Helpers for instantiating name table records."""
+
+from contextlib import contextmanager
+from copy import deepcopy
+from enum import IntEnum
+import re
+
+
+class NameID(IntEnum):
+ FAMILY_NAME = 1
+ SUBFAMILY_NAME = 2
+ UNIQUE_FONT_IDENTIFIER = 3
+ FULL_FONT_NAME = 4
+ VERSION_STRING = 5
+ POSTSCRIPT_NAME = 6
+ TYPOGRAPHIC_FAMILY_NAME = 16
+ TYPOGRAPHIC_SUBFAMILY_NAME = 17
+ VARIATIONS_POSTSCRIPT_NAME_PREFIX = 25
+
+
+ELIDABLE_AXIS_VALUE_NAME = 2
+
+
+def getVariationNameIDs(varfont):
+ used = []
+ if "fvar" in varfont:
+ fvar = varfont["fvar"]
+ for axis in fvar.axes:
+ used.append(axis.axisNameID)
+ for instance in fvar.instances:
+ used.append(instance.subfamilyNameID)
+ if instance.postscriptNameID != 0xFFFF:
+ used.append(instance.postscriptNameID)
+ if "STAT" in varfont:
+ stat = varfont["STAT"].table
+ for axis in stat.DesignAxisRecord.Axis if stat.DesignAxisRecord else ():
+ used.append(axis.AxisNameID)
+ for value in stat.AxisValueArray.AxisValue if stat.AxisValueArray else ():
+ used.append(value.ValueNameID)
+ # nameIDs <= 255 are reserved by OT spec so we don't touch them
+ return {nameID for nameID in used if nameID > 255}
+
+
+@contextmanager
+def pruningUnusedNames(varfont):
+ from . import log
+
+ origNameIDs = getVariationNameIDs(varfont)
+
+ yield
+
+ log.info("Pruning name table")
+ exclude = origNameIDs - getVariationNameIDs(varfont)
+ varfont["name"].names[:] = [
+ record for record in varfont["name"].names if record.nameID not in exclude
+ ]
+ if "ltag" in varfont:
+ # Drop the whole 'ltag' table if all the language-dependent Unicode name
+ # records that reference it have been dropped.
+ # TODO: Only prune unused ltag tags, renumerating langIDs accordingly.
+ # Note ltag can also be used by feat or morx tables, so check those too.
+ if not any(
+ record
+ for record in varfont["name"].names
+ if record.platformID == 0 and record.langID != 0xFFFF
+ ):
+ del varfont["ltag"]
+
+
+def updateNameTable(varfont, axisLimits):
+ """Update instatiated variable font's name table using STAT AxisValues.
+
+ Raises ValueError if the STAT table is missing or an Axis Value table is
+ missing for requested axis locations.
+
+ First, collect all STAT AxisValues that match the new default axis locations
+ (excluding "elided" ones); concatenate the strings in design axis order,
+ while giving priority to "synthetic" values (Format 4), to form the
+ typographic subfamily name associated with the new default instance.
+ Finally, update all related records in the name table, making sure that
+ legacy family/sub-family names conform to the the R/I/B/BI (Regular, Italic,
+ Bold, Bold Italic) naming model.
+
+ Example: Updating a partial variable font:
+ | >>> ttFont = TTFont("OpenSans[wdth,wght].ttf")
+ | >>> updateNameTable(ttFont, {"wght": AxisRange(400, 900), "wdth": 75})
+
+ The name table records will be updated in the following manner:
+ NameID 1 familyName: "Open Sans" --> "Open Sans Condensed"
+ NameID 2 subFamilyName: "Regular" --> "Regular"
+ NameID 3 Unique font identifier: "3.000;GOOG;OpenSans-Regular" --> \
+ "3.000;GOOG;OpenSans-Condensed"
+ NameID 4 Full font name: "Open Sans Regular" --> "Open Sans Condensed"
+ NameID 6 PostScript name: "OpenSans-Regular" --> "OpenSans-Condensed"
+ NameID 16 Typographic Family name: None --> "Open Sans"
+ NameID 17 Typographic Subfamily name: None --> "Condensed"
+
+ References:
+ https://docs.microsoft.com/en-us/typography/opentype/spec/stat
+ https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
+ """
+ from . import AxisRange, axisValuesFromAxisLimits
+
+ if "STAT" not in varfont:
+ raise ValueError("Cannot update name table since there is no STAT table.")
+ stat = varfont["STAT"].table
+ if not stat.AxisValueArray:
+ raise ValueError("Cannot update name table since there are no STAT Axis Values")
+ fvar = varfont["fvar"]
+
+ # The updated name table will reflect the new 'zero origin' of the font.
+ # If we're instantiating a partial font, we will populate the unpinned
+ # axes with their default axis values.
+ fvarDefaults = {a.axisTag: a.defaultValue for a in fvar.axes}
+ defaultAxisCoords = deepcopy(axisLimits)
+ for axisTag, val in fvarDefaults.items():
+ if axisTag not in defaultAxisCoords or isinstance(
+ defaultAxisCoords[axisTag], AxisRange
+ ):
+ defaultAxisCoords[axisTag] = val
+
+ axisValueTables = axisValuesFromAxisLimits(stat, defaultAxisCoords)
+ checkAxisValuesExist(stat, axisValueTables, defaultAxisCoords)
+
+ # ignore "elidable" axis values, should be omitted in application font menus.
+ axisValueTables = [
+ v for v in axisValueTables if not v.Flags & ELIDABLE_AXIS_VALUE_NAME
+ ]
+ axisValueTables = _sortAxisValues(axisValueTables)
+ _updateNameRecords(varfont, axisValueTables)
+
+
+def checkAxisValuesExist(stat, axisValues, axisCoords):
+ seen = set()
+ designAxes = stat.DesignAxisRecord.Axis
+ for axisValueTable in axisValues:
+ axisValueFormat = axisValueTable.Format
+ if axisValueTable.Format in (1, 2, 3):
+ axisTag = designAxes[axisValueTable.AxisIndex].AxisTag
+ if axisValueFormat == 2:
+ axisValue = axisValueTable.NominalValue
+ else:
+ axisValue = axisValueTable.Value
+ if axisTag in axisCoords and axisValue == axisCoords[axisTag]:
+ seen.add(axisTag)
+ elif axisValueTable.Format == 4:
+ for rec in axisValueTable.AxisValueRecord:
+ axisTag = designAxes[rec.AxisIndex].AxisTag
+ if axisTag in axisCoords and rec.Value == axisCoords[axisTag]:
+ seen.add(axisTag)
+
+ missingAxes = set(axisCoords) - seen
+ if missingAxes:
+ missing = ", ".join(f"'{i}={axisCoords[i]}'" for i in missingAxes)
+ raise ValueError(f"Cannot find Axis Values [{missing}]")
+
+
+def _sortAxisValues(axisValues):
+ # Sort by axis index, remove duplicates and ensure that format 4 AxisValues
+ # are dominant.
+ # The MS Spec states: "if a format 1, format 2 or format 3 table has a
+ # (nominal) value used in a format 4 table that also has values for
+ # other axes, the format 4 table, being the more specific match, is used",
+ # https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-4
+ results = []
+ seenAxes = set()
+ # Sort format 4 axes so the tables with the most AxisValueRecords are first
+ format4 = sorted(
+ [v for v in axisValues if v.Format == 4],
+ key=lambda v: len(v.AxisValueRecord),
+ reverse=True,
+ )
+
+ for val in format4:
+ axisIndexes = set(r.AxisIndex for r in val.AxisValueRecord)
+ minIndex = min(axisIndexes)
+ if not seenAxes & axisIndexes:
+ seenAxes |= axisIndexes
+ results.append((minIndex, val))
+
+ for val in axisValues:
+ if val in format4:
+ continue
+ axisIndex = val.AxisIndex
+ if axisIndex not in seenAxes:
+ seenAxes.add(axisIndex)
+ results.append((axisIndex, val))
+
+ return [axisValue for _, axisValue in sorted(results)]
+
+
+def _updateNameRecords(varfont, axisValues):
+ # Update nametable based on the axisValues using the R/I/B/BI model.
+ nametable = varfont["name"]
+ stat = varfont["STAT"].table
+
+ axisValueNameIDs = [a.ValueNameID for a in axisValues]
+ ribbiNameIDs = [n for n in axisValueNameIDs if _isRibbi(nametable, n)]
+ nonRibbiNameIDs = [n for n in axisValueNameIDs if n not in ribbiNameIDs]
+ elidedNameID = stat.ElidedFallbackNameID
+ elidedNameIsRibbi = _isRibbi(nametable, elidedNameID)
+
+ getName = nametable.getName
+ platforms = set((r.platformID, r.platEncID, r.langID) for r in nametable.names)
+ for platform in platforms:
+ if not all(getName(i, *platform) for i in (1, 2, elidedNameID)):
+ # Since no family name and subfamily name records were found,
+ # we cannot update this set of name Records.
+ continue
+
+ subFamilyName = " ".join(
+ getName(n, *platform).toUnicode() for n in ribbiNameIDs
+ )
+ if nonRibbiNameIDs:
+ typoSubFamilyName = " ".join(
+ getName(n, *platform).toUnicode() for n in axisValueNameIDs
+ )
+ else:
+ typoSubFamilyName = None
+
+ # If neither subFamilyName and typographic SubFamilyName exist,
+ # we will use the STAT's elidedFallbackName
+ if not typoSubFamilyName and not subFamilyName:
+ if elidedNameIsRibbi:
+ subFamilyName = getName(elidedNameID, *platform).toUnicode()
+ else:
+ typoSubFamilyName = getName(elidedNameID, *platform).toUnicode()
+
+ familyNameSuffix = " ".join(
+ getName(n, *platform).toUnicode() for n in nonRibbiNameIDs
+ )
+
+ _updateNameTableStyleRecords(
+ varfont,
+ familyNameSuffix,
+ subFamilyName,
+ typoSubFamilyName,
+ *platform,
+ )
+
+
+def _isRibbi(nametable, nameID):
+ englishRecord = nametable.getName(nameID, 3, 1, 0x409)
+ return (
+ True
+ if englishRecord is not None
+ and englishRecord.toUnicode() in ("Regular", "Italic", "Bold", "Bold Italic")
+ else False
+ )
+
+
+def _updateNameTableStyleRecords(
+ varfont,
+ familyNameSuffix,
+ subFamilyName,
+ typoSubFamilyName,
+ platformID=3,
+ platEncID=1,
+ langID=0x409,
+):
+ # TODO (Marc F) It may be nice to make this part a standalone
+ # font renamer in the future.
+ nametable = varfont["name"]
+ platform = (platformID, platEncID, langID)
+
+ currentFamilyName = nametable.getName(
+ NameID.TYPOGRAPHIC_FAMILY_NAME, *platform
+ ) or nametable.getName(NameID.FAMILY_NAME, *platform)
+
+ currentStyleName = nametable.getName(
+ NameID.TYPOGRAPHIC_SUBFAMILY_NAME, *platform
+ ) or nametable.getName(NameID.SUBFAMILY_NAME, *platform)
+
+ if not all([currentFamilyName, currentStyleName]):
+ raise ValueError(f"Missing required NameIDs 1 and 2 for platform {platform}")
+
+ currentFamilyName = currentFamilyName.toUnicode()
+ currentStyleName = currentStyleName.toUnicode()
+
+ nameIDs = {
+ NameID.FAMILY_NAME: currentFamilyName,
+ NameID.SUBFAMILY_NAME: subFamilyName or "Regular",
+ }
+ if typoSubFamilyName:
+ nameIDs[NameID.FAMILY_NAME] = f"{currentFamilyName} {familyNameSuffix}".strip()
+ nameIDs[NameID.TYPOGRAPHIC_FAMILY_NAME] = currentFamilyName
+ nameIDs[NameID.TYPOGRAPHIC_SUBFAMILY_NAME] = typoSubFamilyName
+ else:
+ # Remove previous Typographic Family and SubFamily names since they're
+ # no longer required
+ for nameID in (
+ NameID.TYPOGRAPHIC_FAMILY_NAME,
+ NameID.TYPOGRAPHIC_SUBFAMILY_NAME,
+ ):
+ nametable.removeNames(nameID=nameID)
+
+ newFamilyName = (
+ nameIDs.get(NameID.TYPOGRAPHIC_FAMILY_NAME) or nameIDs[NameID.FAMILY_NAME]
+ )
+ newStyleName = (
+ nameIDs.get(NameID.TYPOGRAPHIC_SUBFAMILY_NAME) or nameIDs[NameID.SUBFAMILY_NAME]
+ )
+
+ nameIDs[NameID.FULL_FONT_NAME] = f"{newFamilyName} {newStyleName}"
+ nameIDs[NameID.POSTSCRIPT_NAME] = _updatePSNameRecord(
+ varfont, newFamilyName, newStyleName, platform
+ )
+
+ uniqueID = _updateUniqueIdNameRecord(varfont, nameIDs, platform)
+ if uniqueID:
+ nameIDs[NameID.UNIQUE_FONT_IDENTIFIER] = uniqueID
+
+ for nameID, string in nameIDs.items():
+ assert string, nameID
+ nametable.setName(string, nameID, *platform)
+
+ if "fvar" not in varfont:
+ nametable.removeNames(NameID.VARIATIONS_POSTSCRIPT_NAME_PREFIX)
+
+
+def _updatePSNameRecord(varfont, familyName, styleName, platform):
+ # Implementation based on Adobe Technical Note #5902 :
+ # https://wwwimages2.adobe.com/content/dam/acom/en/devnet/font/pdfs/5902.AdobePSNameGeneration.pdf
+ nametable = varfont["name"]
+
+ family_prefix = nametable.getName(
+ NameID.VARIATIONS_POSTSCRIPT_NAME_PREFIX, *platform
+ )
+ if family_prefix:
+ family_prefix = family_prefix.toUnicode()
+ else:
+ family_prefix = familyName
+
+ psName = f"{family_prefix}-{styleName}"
+ # Remove any characters other than uppercase Latin letters, lowercase
+ # Latin letters, digits and hyphens.
+ psName = re.sub(r"[^A-Za-z0-9-]", r"", psName)
+
+ if len(psName) > 127:
+ # Abbreviating the stylename so it fits within 127 characters whilst
+ # conforming to every vendor's specification is too complex. Instead
+ # we simply truncate the psname and add the required "..."
+ return f"{psName[:124]}..."
+ return psName
+
+
+def _updateUniqueIdNameRecord(varfont, nameIDs, platform):
+ nametable = varfont["name"]
+ currentRecord = nametable.getName(NameID.UNIQUE_FONT_IDENTIFIER, *platform)
+ if not currentRecord:
+ return None
+
+ # Check if full name and postscript name are a substring of currentRecord
+ for nameID in (NameID.FULL_FONT_NAME, NameID.POSTSCRIPT_NAME):
+ nameRecord = nametable.getName(nameID, *platform)
+ if not nameRecord:
+ continue
+ if nameRecord.toUnicode() in currentRecord.toUnicode():
+ return currentRecord.toUnicode().replace(
+ nameRecord.toUnicode(), nameIDs[nameRecord.nameID]
+ )
+
+ # Create a new string since we couldn't find any substrings.
+ fontVersion = _fontVersion(varfont, platform)
+ achVendID = varfont["OS/2"].achVendID
+ # Remove non-ASCII characers and trailing spaces
+ vendor = re.sub(r"[^\x00-\x7F]", "", achVendID).strip()
+ psName = nameIDs[NameID.POSTSCRIPT_NAME]
+ return f"{fontVersion};{vendor};{psName}"
+
+
+def _fontVersion(font, platform=(3, 1, 0x409)):
+ nameRecord = font["name"].getName(NameID.VERSION_STRING, *platform)
+ if nameRecord is None:
+ return f'{font["head"].fontRevision:.3f}'
+ # "Version 1.101; ttfautohint (v1.8.1.43-b0c9)" --> "1.101"
+ # Also works fine with inputs "Version 1.101" or "1.101" etc
+ versionNumber = nameRecord.toUnicode().split(";")[0]
+ return versionNumber.lstrip("Version ").strip()
diff --git a/Lib/fontTools/varLib/interpolatable.py b/Lib/fontTools/varLib/interpolatable.py
index d4feed27..cff76ece 100644
--- a/Lib/fontTools/varLib/interpolatable.py
+++ b/Lib/fontTools/varLib/interpolatable.py
@@ -6,176 +6,367 @@ Call as:
$ fonttools varLib.interpolatable font1 font2 ...
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
from fontTools.pens.basePen import AbstractPen, BasePen
from fontTools.pens.recordingPen import RecordingPen
from fontTools.pens.statisticsPen import StatisticsPen
+from fontTools.pens.momentsPen import OpenContourError
+from collections import OrderedDict
import itertools
+import sys
class PerContourPen(BasePen):
- def __init__(self, Pen, glyphset=None):
- BasePen.__init__(self, glyphset)
- self._glyphset = glyphset
- self._Pen = Pen
- self._pen = None
- self.value = []
- def _moveTo(self, p0):
- self._newItem()
- self._pen.moveTo(p0)
- def _lineTo(self, p1):
- self._pen.lineTo(p1)
- def _qCurveToOne(self, p1, p2):
- self._pen.qCurveTo(p1, p2)
- def _curveToOne(self, p1, p2, p3):
- self._pen.curveTo(p1, p2, p3)
- def _closePath(self):
- self._pen.closePath()
- self._pen = None
- def _endPath(self):
- self._pen.endPath()
- self._pen = None
-
- def _newItem(self):
- self._pen = pen = self._Pen()
- self.value.append(pen)
+ def __init__(self, Pen, glyphset=None):
+ BasePen.__init__(self, glyphset)
+ self._glyphset = glyphset
+ self._Pen = Pen
+ self._pen = None
+ self.value = []
-class PerContourOrComponentPen(PerContourPen):
+ def _moveTo(self, p0):
+ self._newItem()
+ self._pen.moveTo(p0)
+
+ def _lineTo(self, p1):
+ self._pen.lineTo(p1)
+
+ def _qCurveToOne(self, p1, p2):
+ self._pen.qCurveTo(p1, p2)
+
+ def _curveToOne(self, p1, p2, p3):
+ self._pen.curveTo(p1, p2, p3)
+
+ def _closePath(self):
+ self._pen.closePath()
+ self._pen = None
- def addComponent(self, glyphName, transformation):
- self._newItem()
- self.value[-1].addComponent(glyphName, transformation)
+ def _endPath(self):
+ self._pen.endPath()
+ self._pen = None
+
+ def _newItem(self):
+ self._pen = pen = self._Pen()
+ self.value.append(pen)
+
+
+class PerContourOrComponentPen(PerContourPen):
+ def addComponent(self, glyphName, transformation):
+ self._newItem()
+ self.value[-1].addComponent(glyphName, transformation)
def _vdiff(v0, v1):
- return tuple(b-a for a,b in zip(v0,v1))
+ return tuple(b - a for a, b in zip(v0, v1))
+
+
def _vlen(vec):
- v = 0
- for x in vec:
- v += x*x
- return v
+ v = 0
+ for x in vec:
+ v += x * x
+ return v
+
def _matching_cost(G, matching):
- return sum(G[i][j] for i,j in enumerate(matching))
+ return sum(G[i][j] for i, j in enumerate(matching))
+
def min_cost_perfect_bipartite_matching(G):
- n = len(G)
- try:
- from scipy.optimize import linear_sum_assignment
- rows, cols = linear_sum_assignment(G)
- assert (rows == list(range(n))).all()
- return list(cols), _matching_cost(G, cols)
- except ImportError:
- pass
-
- try:
- from munkres import Munkres
- cols = [None] * n
- for row,col in Munkres().compute(G):
- cols[row] = col
- return cols, _matching_cost(G, cols)
- except ImportError:
- pass
-
- if n > 6:
- raise Exception("Install Python module 'munkres' or 'scipy >= 0.17.0'")
-
- # Otherwise just brute-force
- permutations = itertools.permutations(range(n))
- best = list(next(permutations))
- best_cost = _matching_cost(G, best)
- for p in permutations:
- cost = _matching_cost(G, p)
- if cost < best_cost:
- best, best_cost = list(p), cost
- return best, best_cost
+ n = len(G)
+ try:
+ from scipy.optimize import linear_sum_assignment
+
+ rows, cols = linear_sum_assignment(G)
+ assert (rows == list(range(n))).all()
+ return list(cols), _matching_cost(G, cols)
+ except ImportError:
+ pass
+
+ try:
+ from munkres import Munkres
+
+ cols = [None] * n
+ for row, col in Munkres().compute(G):
+ cols[row] = col
+ return cols, _matching_cost(G, cols)
+ except ImportError:
+ pass
+
+ if n > 6:
+ raise Exception("Install Python module 'munkres' or 'scipy >= 0.17.0'")
+
+ # Otherwise just brute-force
+ permutations = itertools.permutations(range(n))
+ best = list(next(permutations))
+ best_cost = _matching_cost(G, best)
+ for p in permutations:
+ cost = _matching_cost(G, p)
+ if cost < best_cost:
+ best, best_cost = list(p), cost
+ return best, best_cost
def test(glyphsets, glyphs=None, names=None):
- if names is None:
- names = glyphsets
- if glyphs is None:
- glyphs = glyphsets[0].keys()
-
- hist = []
- for glyph_name in glyphs:
- #print()
- #print(glyph_name)
-
- try:
- allVectors = []
- for glyphset,name in zip(glyphsets, names):
- #print('.', end='')
- glyph = glyphset[glyph_name]
-
- perContourPen = PerContourOrComponentPen(RecordingPen, glyphset=glyphset)
- glyph.draw(perContourPen)
- contourPens = perContourPen.value
- del perContourPen
-
- contourVectors = []
- allVectors.append(contourVectors)
- for contour in contourPens:
- stats = StatisticsPen(glyphset=glyphset)
- contour.replay(stats)
- size = abs(stats.area) ** .5 * .5
- vector = (
- int(size),
- int(stats.meanX),
- int(stats.meanY),
- int(stats.stddevX * 2),
- int(stats.stddevY * 2),
- int(stats.correlation * size),
- )
- contourVectors.append(vector)
- #print(vector)
-
- # Check each master against the next one in the list.
- for i,(m0,m1) in enumerate(zip(allVectors[:-1],allVectors[1:])):
- if len(m0) != len(m1):
- print('%s: %s+%s: Glyphs not compatible!!!!!' % (glyph_name, names[i], names[i+1]))
- continue
- if not m0:
- continue
- costs = [[_vlen(_vdiff(v0,v1)) for v1 in m1] for v0 in m0]
- matching, matching_cost = min_cost_perfect_bipartite_matching(costs)
- if matching != list(range(len(m0))):
- print('%s: %s+%s: Glyph has wrong contour/component order: %s' % (glyph_name, names[i], names[i+1], matching)) #, m0, m1)
- break
- upem = 2048
- item_cost = round((matching_cost / len(m0) / len(m0[0])) ** .5 / upem * 100)
- hist.append(item_cost)
- threshold = 7
- if item_cost >= threshold:
- print('%s: %s+%s: Glyph has very high cost: %d%%' % (glyph_name, names[i], names[i+1], item_cost))
-
-
- except ValueError as e:
- print('%s: %s: math error %s; skipping glyph.' % (glyph_name, name, e))
- print(contour.value)
- #raise
- #for x in hist:
- # print(x)
-
-def main(args):
- filenames = args
- glyphs = None
- #glyphs = ['uni08DB', 'uniFD76']
- #glyphs = ['uni08DE', 'uni0034']
- #glyphs = ['uni08DE', 'uni0034', 'uni0751', 'uni0753', 'uni0754', 'uni08A4', 'uni08A4.fina', 'uni08A5.fina']
-
- from os.path import basename
- names = [basename(filename).rsplit('.', 1)[0] for filename in filenames]
-
- from fontTools.ttLib import TTFont
- fonts = [TTFont(filename) for filename in filenames]
-
- glyphsets = [font.getGlyphSet() for font in fonts]
- test(glyphsets, glyphs=glyphs, names=names)
-
-if __name__ == '__main__':
- import sys
- main(sys.argv[1:])
+ if names is None:
+ names = glyphsets
+ if glyphs is None:
+ glyphs = glyphsets[0].keys()
+
+ hist = []
+ problems = OrderedDict()
+
+ def add_problem(glyphname, problem):
+ problems.setdefault(glyphname, []).append(problem)
+
+ for glyph_name in glyphs:
+ # print()
+ # print(glyph_name)
+
+ try:
+ allVectors = []
+ allNodeTypes = []
+ for glyphset, name in zip(glyphsets, names):
+ # print('.', end='')
+ if glyph_name not in glyphset:
+ add_problem(glyph_name, {"type": "missing", "master": name})
+ continue
+ glyph = glyphset[glyph_name]
+
+ perContourPen = PerContourOrComponentPen(
+ RecordingPen, glyphset=glyphset
+ )
+ glyph.draw(perContourPen)
+ contourPens = perContourPen.value
+ del perContourPen
+
+ contourVectors = []
+ nodeTypes = []
+ allNodeTypes.append(nodeTypes)
+ allVectors.append(contourVectors)
+ for ix, contour in enumerate(contourPens):
+ nodeTypes.append(
+ tuple(instruction[0] for instruction in contour.value)
+ )
+ stats = StatisticsPen(glyphset=glyphset)
+ try:
+ contour.replay(stats)
+ except OpenContourError as e:
+ add_problem(
+ glyph_name,
+ {"master": name, "contour": ix, "type": "open_path"},
+ )
+ continue
+ size = abs(stats.area) ** 0.5 * 0.5
+ vector = (
+ int(size),
+ int(stats.meanX),
+ int(stats.meanY),
+ int(stats.stddevX * 2),
+ int(stats.stddevY * 2),
+ int(stats.correlation * size),
+ )
+ contourVectors.append(vector)
+ # print(vector)
+
+ # Check each master against the next one in the list.
+ for i, (m0, m1) in enumerate(zip(allNodeTypes[:-1], allNodeTypes[1:])):
+ if len(m0) != len(m1):
+ add_problem(
+ glyph_name,
+ {
+ "type": "path_count",
+ "master_1": names[i],
+ "master_2": names[i + 1],
+ "value_1": len(m0),
+ "value_2": len(m1),
+ },
+ )
+ if m0 == m1:
+ continue
+ for pathIx, (nodes1, nodes2) in enumerate(zip(m0, m1)):
+ if nodes1 == nodes2:
+ continue
+ if len(nodes1) != len(nodes2):
+ add_problem(
+ glyph_name,
+ {
+ "type": "node_count",
+ "path": pathIx,
+ "master_1": names[i],
+ "master_2": names[i + 1],
+ "value_1": len(nodes1),
+ "value_2": len(nodes2),
+ },
+ )
+ continue
+ for nodeIx, (n1, n2) in enumerate(zip(nodes1, nodes2)):
+ if n1 != n2:
+ add_problem(
+ glyph_name,
+ {
+ "type": "node_incompatibility",
+ "path": pathIx,
+ "node": nodeIx,
+ "master_1": names[i],
+ "master_2": names[i + 1],
+ "value_1": n1,
+ "value_2": n2,
+ },
+ )
+ continue
+
+ for i, (m0, m1) in enumerate(zip(allVectors[:-1], allVectors[1:])):
+ if len(m0) != len(m1):
+ # We already reported this
+ continue
+ if not m0:
+ continue
+ costs = [[_vlen(_vdiff(v0, v1)) for v1 in m1] for v0 in m0]
+ matching, matching_cost = min_cost_perfect_bipartite_matching(costs)
+ if matching != list(range(len(m0))):
+ add_problem(
+ glyph_name,
+ {
+ "type": "contour_order",
+ "master_1": names[i],
+ "master_2": names[i + 1],
+ "value_1": list(range(len(m0))),
+ "value_2": matching,
+ },
+ )
+ break
+ upem = 2048
+ item_cost = round(
+ (matching_cost / len(m0) / len(m0[0])) ** 0.5 / upem * 100
+ )
+ hist.append(item_cost)
+ threshold = 7
+ if item_cost >= threshold:
+ add_problem(
+ glyph_name,
+ {
+ "type": "high_cost",
+ "master_1": names[i],
+ "master_2": names[i + 1],
+ "value_1": item_cost,
+ "value_2": threshold,
+ },
+ )
+
+ except ValueError as e:
+ add_problem(
+ glyph_name,
+ {"type": "math_error", "master": name, "error": e},
+ )
+ return problems
+
+
+def main(args=None):
+ """Test for interpolatability issues between fonts"""
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ "fonttools varLib.interpolatable",
+ description=main.__doc__,
+ )
+ parser.add_argument(
+ "--json",
+ action="store_true",
+ help="Output report in JSON format",
+ )
+ parser.add_argument(
+ "inputs", metavar="FILE", type=str, nargs="+", help="Input TTF/UFO files"
+ )
+
+ args = parser.parse_args(args)
+ glyphs = None
+ # glyphs = ['uni08DB', 'uniFD76']
+ # glyphs = ['uni08DE', 'uni0034']
+ # glyphs = ['uni08DE', 'uni0034', 'uni0751', 'uni0753', 'uni0754', 'uni08A4', 'uni08A4.fina', 'uni08A5.fina']
+
+ from os.path import basename
+
+ names = [basename(filename).rsplit(".", 1)[0] for filename in args.inputs]
+
+ fonts = []
+ for filename in args.inputs:
+ if filename.endswith(".ufo"):
+ from fontTools.ufoLib import UFOReader
+
+ fonts.append(UFOReader(filename))
+ else:
+ from fontTools.ttLib import TTFont
+
+ fonts.append(TTFont(filename))
+
+ glyphsets = [font.getGlyphSet() for font in fonts]
+ problems = test(glyphsets, glyphs=glyphs, names=names)
+ if args.json:
+ import json
+
+ print(json.dumps(problems))
+ else:
+ for glyph, glyph_problems in problems.items():
+ print(f"Glyph {glyph} was not compatible: ")
+ for p in glyph_problems:
+ if p["type"] == "missing":
+ print(" Glyph was missing in master %s" % p["master"])
+ if p["type"] == "open_path":
+ print(" Glyph has an open path in master %s" % p["master"])
+ if p["type"] == "path_count":
+ print(
+ " Path count differs: %i in %s, %i in %s"
+ % (p["value_1"], p["master_1"], p["value_2"], p["master_2"])
+ )
+ if p["type"] == "node_count":
+ print(
+ " Node count differs in path %i: %i in %s, %i in %s"
+ % (
+ p["path"],
+ p["value_1"],
+ p["master_1"],
+ p["value_2"],
+ p["master_2"],
+ )
+ )
+ if p["type"] == "node_incompatibility":
+ print(
+ " Node %o incompatible in path %i: %s in %s, %s in %s"
+ % (
+ p["node"],
+ p["path"],
+ p["value_1"],
+ p["master_1"],
+ p["value_2"],
+ p["master_2"],
+ )
+ )
+ if p["type"] == "contour_order":
+ print(
+ " Contour order differs: %s in %s, %s in %s"
+ % (
+ p["value_1"],
+ p["master_1"],
+ p["value_2"],
+ p["master_2"],
+ )
+ )
+ if p["type"] == "high_cost":
+ print(
+ " Interpolation has high cost: cost of %s to %s = %i, threshold %i"
+ % (
+ p["master_1"],
+ p["master_2"],
+ p["value_1"],
+ p["value_2"],
+ )
+ )
+ if problems:
+ return problems
+
+
+if __name__ == "__main__":
+ import sys
+
+ problems = main()
+ sys.exit(int(bool(problems)))
diff --git a/Lib/fontTools/varLib/interpolate_layout.py b/Lib/fontTools/varLib/interpolate_layout.py
index f252149a..6d0385dd 100644
--- a/Lib/fontTools/varLib/interpolate_layout.py
+++ b/Lib/fontTools/varLib/interpolate_layout.py
@@ -1,8 +1,6 @@
"""
Interpolate OpenType Layout tables (GDEF / GPOS / GSUB).
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.varLib import models, VarLibError, load_designspace, load_masters
from fontTools.varLib.merger import InstancerMerger
@@ -60,29 +58,42 @@ def interpolate_layout(designspace, loc, master_finder=lambda s:s, mapped=False)
def main(args=None):
+ """Interpolate GDEF/GPOS/GSUB tables for a point on a designspace"""
from fontTools import configLogger
-
+ import argparse
import sys
- if args is None:
- args = sys.argv[1:]
- designspace_filename = args[0]
- locargs = args[1:]
- outfile = os.path.splitext(designspace_filename)[0] + '-instance.ttf'
+ parser = argparse.ArgumentParser(
+ "fonttools varLib.interpolate_layout",
+ description=main.__doc__,
+ )
+ parser.add_argument('designspace_filename', metavar='DESIGNSPACE',
+ help="Input TTF files")
+ parser.add_argument('locations', metavar='LOCATION', type=str, nargs='+',
+ help="Axis locations (e.g. wdth=120")
+ parser.add_argument('-o', '--output', metavar='OUTPUT',
+ help="Output font file (defaults to <designspacename>-instance.ttf)")
+ parser.add_argument('-l', '--loglevel', metavar='LEVEL', default="INFO",
+ help="Logging level (defaults to INFO)")
+
+
+ args = parser.parse_args(args)
+
+ if not args.output:
+ args.output = os.path.splitext(args.designspace_filename)[0] + '-instance.ttf'
- # TODO: allow user to configure logging via command-line options
- configLogger(level="INFO")
+ configLogger(level=args.loglevel)
finder = lambda s: s.replace('master_ufo', 'master_ttf_interpolatable').replace('.ufo', '.ttf')
loc = {}
- for arg in locargs:
+ for arg in args.locations:
tag,val = arg.split('=')
loc[tag] = float(val)
- font = interpolate_layout(designspace_filename, loc, finder)
- log.info("Saving font %s", outfile)
- font.save(outfile)
+ font = interpolate_layout(args.designspace_filename, loc, finder)
+ log.info("Saving font %s", args.output)
+ font.save(args.output)
if __name__ == "__main__":
diff --git a/Lib/fontTools/varLib/iup.py b/Lib/fontTools/varLib/iup.py
index fc36a9f5..45a7a5ed 100644
--- a/Lib/fontTools/varLib/iup.py
+++ b/Lib/fontTools/varLib/iup.py
@@ -1,8 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
-
-
def iup_segment(coords, rc1, rd1, rc2, rd2):
# rc1 = reference coord 1
# rd1 = reference delta 1
diff --git a/Lib/fontTools/varLib/merger.py b/Lib/fontTools/varLib/merger.py
index 9b5af5ca..c9d14381 100644
--- a/Lib/fontTools/varLib/merger.py
+++ b/Lib/fontTools/varLib/merger.py
@@ -1,12 +1,10 @@
"""
Merge OpenType Layout tables (GDEF / GPOS / GSUB).
"""
-from __future__ import print_function, division, absolute_import
import copy
from operator import ior
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
from fontTools.misc import classifyTools
+from fontTools.misc.roundTools import otRound
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables import otBase as otBase
from fontTools.ttLib.tables.DefaultTable import DefaultTable
@@ -16,6 +14,18 @@ from fontTools.varLib.varStore import VarStoreInstancer
from functools import reduce
from fontTools.otlLib.builder import buildSinglePos
+from .errors import (
+ ShouldBeConstant,
+ FoundANone,
+ MismatchedTypes,
+ LengthsDiffer,
+ KeysDiffer,
+ InconsistentGlyphOrder,
+ InconsistentExtensions,
+ UnsupportedFormat,
+ UnsupportedFormat,
+ VarLibMergeError,
+)
class Merger(object):
@@ -62,9 +72,16 @@ class Merger(object):
return _default
def mergeObjects(self, out, lst, exclude=()):
+ if hasattr(out, "ensureDecompiled"):
+ out.ensureDecompiled()
+ for item in lst:
+ if hasattr(item, "ensureDecompiled"):
+ item.ensureDecompiled()
keys = sorted(vars(out).keys())
- assert all(keys == sorted(vars(v).keys()) for v in lst), \
- (keys, [sorted(vars(v).keys()) for v in lst])
+ if not all(keys == sorted(vars(v).keys()) for v in lst):
+ raise KeysDiffer(self, expected=keys,
+ got=[sorted(vars(v).keys()) for v in lst]
+ )
mergers = self.mergersFor(out)
defaultMerger = mergers.get('*', self.__class__.mergeThings)
try:
@@ -74,41 +91,47 @@ class Merger(object):
values = [getattr(table, key) for table in lst]
mergerFunc = mergers.get(key, defaultMerger)
mergerFunc(self, value, values)
- except Exception as e:
- e.args = e.args + ('.'+key,)
+ except VarLibMergeError as e:
+ e.stack.append('.'+key)
raise
def mergeLists(self, out, lst):
- assert allEqualTo(out, lst, len), (len(out), [len(v) for v in lst])
+ if not allEqualTo(out, lst, len):
+ raise LengthsDiffer(self, expected=len(out), got=[len(x) for x in lst])
for i,(value,values) in enumerate(zip(out, zip(*lst))):
try:
self.mergeThings(value, values)
- except Exception as e:
- e.args = e.args + ('[%d]' % i,)
+ except VarLibMergeError as e:
+ e.stack.append('[%d]' % i)
raise
def mergeThings(self, out, lst):
- try:
- assert allEqualTo(out, lst, type), (out, lst)
- mergerFunc = self.mergersFor(out).get(None, None)
- if mergerFunc is not None:
- mergerFunc(self, out, lst)
- elif hasattr(out, '__dict__'):
- self.mergeObjects(out, lst)
- elif isinstance(out, list):
- self.mergeLists(out, lst)
- else:
- assert allEqualTo(out, lst), (out, lst)
- except Exception as e:
- e.args = e.args + (type(out).__name__,)
- raise
+ if not allEqualTo(out, lst, type):
+ raise MismatchedTypes(self,
+ expected=type(out).__name__,
+ got=[type(x).__name__ for x in lst]
+ )
+ mergerFunc = self.mergersFor(out).get(None, None)
+ if mergerFunc is not None:
+ mergerFunc(self, out, lst)
+ elif hasattr(out, '__dict__'):
+ self.mergeObjects(out, lst)
+ elif isinstance(out, list):
+ self.mergeLists(out, lst)
+ else:
+ if not allEqualTo(out, lst):
+ raise ShouldBeConstant(self, expected=out, got=lst)
def mergeTables(self, font, master_ttfs, tableTags):
-
for tag in tableTags:
if tag not in font: continue
- self.mergeThings(font[tag], [m[tag] if tag in m else None
- for m in master_ttfs])
+ try:
+ self.ttfs = [m for m in master_ttfs if tag in m]
+ self.mergeThings(font[tag], [m[tag] if tag in m else None
+ for m in master_ttfs])
+ except VarLibMergeError as e:
+ e.stack.append(tag)
+ raise
#
# Aligning merger
@@ -119,7 +142,8 @@ class AligningMerger(Merger):
@AligningMerger.merger(ot.GDEF, "GlyphClassDef")
def merge(merger, self, lst):
if self is None:
- assert allNone(lst), (lst)
+ if not allNone(lst):
+ raise NotANone(self, expected=None, got=lst)
return
lst = [l.classDefs for l in lst]
@@ -131,7 +155,8 @@ def merge(merger, self, lst):
allKeys.update(*[l.keys() for l in lst])
for k in allKeys:
allValues = nonNone(l.get(k) for l in lst)
- assert allEqual(allValues), allValues
+ if not allEqual(allValues):
+ raise ShouldBeConstant(self, expected=allValues[0], got=lst, stack="."+k)
if not allValues:
self[k] = None
else:
@@ -144,7 +169,7 @@ def _SinglePosUpgradeToFormat2(self):
ret.Format = 2
ret.Coverage = self.Coverage
ret.ValueFormat = self.ValueFormat
- ret.Value = [self.Value for g in ret.Coverage.glyphs]
+ ret.Value = [self.Value for _ in ret.Coverage.glyphs]
ret.ValueCount = len(ret.Value)
return ret
@@ -167,7 +192,8 @@ def _merge_GlyphOrders(font, lst, values_lst=None, default=None):
sortKey = font.getReverseGlyphMap().__getitem__
order = sorted(combined, key=sortKey)
# Make sure all input glyphsets were in proper order
- assert all(sorted(vs, key=sortKey) == vs for vs in lst)
+ if not all(sorted(vs, key=sortKey) == vs for vs in lst):
+ raise InconsistentGlyphOrder(self)
del combined
paddedValues = None
@@ -194,7 +220,7 @@ def _Lookup_SinglePos_get_effective_value(subtables, glyph):
elif self.Format == 2:
return self.Value[self.Coverage.glyphs.index(glyph)]
else:
- assert 0
+ raise UnsupportedFormat(self, subtable="single positioning lookup")
return None
def _Lookup_PairPos_get_effective_value_pair(subtables, firstGlyph, secondGlyph):
@@ -216,13 +242,14 @@ def _Lookup_PairPos_get_effective_value_pair(subtables, firstGlyph, secondGlyph)
klass2 = self.ClassDef2.classDefs.get(secondGlyph, 0)
return self.Class1Record[klass1].Class2Record[klass2]
else:
- assert 0
+ raise UnsupportedFormat(self, subtable="pair positioning lookup")
return None
@AligningMerger.merger(ot.SinglePos)
def merge(merger, self, lst):
self.ValueFormat = valueFormat = reduce(int.__or__, [l.ValueFormat for l in lst], 0)
- assert len(lst) == 1 or (valueFormat & ~0xF == 0), valueFormat
+ if not (len(lst) == 1 or (valueFormat & ~0xF == 0)):
+ raise UnsupportedFormat(self, subtable="single positioning lookup")
# If all have same coverage table and all are format 1,
coverageGlyphs = self.Coverage.glyphs
@@ -242,7 +269,7 @@ def merge(merger, self, lst):
[v.Value for v in lst])
self.Coverage.glyphs = glyphs
- self.Value = [otBase.ValueRecord(valueFormat) for g in glyphs]
+ self.Value = [otBase.ValueRecord(valueFormat) for _ in glyphs]
self.ValueCount = len(self.Value)
for i,values in enumerate(padded):
@@ -321,7 +348,7 @@ def _PairPosFormat1_merge(self, lst, merger):
default=empty)
self.Coverage.glyphs = glyphs
- self.PairSet = [ot.PairSet() for g in glyphs]
+ self.PairSet = [ot.PairSet() for _ in glyphs]
self.PairSetCount = len(self.PairSet)
for glyph, ps in zip(glyphs, self.PairSet):
ps._firstGlyph = glyph
@@ -382,28 +409,12 @@ def _ClassDef_merge_classify(lst, allGlyphses=None):
return self, classes
-# It's stupid that we need to do this here. Just need to, to match test
-# expecatation results, since ttx prints out format of ClassDef (and Coverage)
-# even though it should not.
-def _ClassDef_calculate_Format(self, font):
- fmt = 2
- ranges = self._getClassRanges(font)
- if ranges:
- startGlyph = ranges[0][1]
- endGlyph = ranges[-1][3]
- glyphCount = endGlyph - startGlyph + 1
- if len(ranges) * 3 >= glyphCount + 1:
- # Format 1 is more compact
- fmt = 1
- self.Format = fmt
-
def _PairPosFormat2_align_matrices(self, lst, font, transparent=False):
matrices = [l.Class1Record for l in lst]
# Align first classes
self.ClassDef1, classes = _ClassDef_merge_classify([l.ClassDef1 for l in lst], [l.Coverage.glyphs for l in lst])
- _ClassDef_calculate_Format(self.ClassDef1, font)
self.Class1Count = len(classes)
new_matrices = []
for l,matrix in zip(lst, matrices):
@@ -442,7 +453,6 @@ def _PairPosFormat2_align_matrices(self, lst, font, transparent=False):
# Align second classes
self.ClassDef2, classes = _ClassDef_merge_classify([l.ClassDef2 for l in lst])
- _ClassDef_calculate_Format(self.ClassDef2, font)
self.Class2Count = len(classes)
new_matrices = []
for l,matrix in zip(lst, matrices):
@@ -508,7 +518,7 @@ def merge(merger, self, lst):
elif self.Format == 2:
_PairPosFormat2_merge(self, lst, merger)
else:
- assert False
+ raise UnsupportedFormat(self, subtable="pair positioning lookup")
del merger.valueFormat1, merger.valueFormat2
@@ -573,8 +583,8 @@ def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'):
# failures in that case will probably signify mistakes in the
# input masters.
- assert allEqual(allClasses), allClasses
- if not allClasses:
+ if not allEqual(allClasses):
+ raise allClasses(self, allClasses)
rec = None
else:
rec = ot.MarkRecord()
@@ -622,25 +632,33 @@ def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'):
@AligningMerger.merger(ot.MarkBasePos)
def merge(merger, self, lst):
- assert allEqualTo(self.Format, (l.Format for l in lst))
+ if not allEqualTo(self.Format, (l.Format for l in lst)):
+ raise InconsistentFormats(self,
+ subtable="mark-to-base positioning lookup",
+ expected=self.Format,
+ got=[l.Format for l in lst]
+ )
if self.Format == 1:
_MarkBasePosFormat1_merge(self, lst, merger)
else:
- assert False
+ raise UnsupportedFormat(self, subtable="mark-to-base positioning lookup")
@AligningMerger.merger(ot.MarkMarkPos)
def merge(merger, self, lst):
- assert allEqualTo(self.Format, (l.Format for l in lst))
+ if not allEqualTo(self.Format, (l.Format for l in lst)):
+ raise InconsistentFormats(self,
+ subtable="mark-to-mark positioning lookup",
+ expected=self.Format,
+ got=[l.Format for l in lst]
+ )
if self.Format == 1:
_MarkBasePosFormat1_merge(self, lst, merger, 'Mark1', 'Mark2')
else:
- assert False
-
+ raise UnsupportedFormat(self, subtable="mark-to-mark positioning lookup")
def _PairSet_flatten(lst, font):
self = ot.PairSet()
self.Coverage = ot.Coverage()
- self.Coverage.Format = 1
# Align them
glyphs, padded = _merge_GlyphOrders(font,
@@ -666,7 +684,6 @@ def _Lookup_PairPosFormat1_subtables_flatten(lst, font):
self = ot.PairPos()
self.Format = 1
self.Coverage = ot.Coverage()
- self.Coverage.Format = 1
self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0)
self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0)
@@ -687,7 +704,6 @@ def _Lookup_PairPosFormat2_subtables_flatten(lst, font):
self = ot.PairPos()
self.Format = 2
self.Coverage = ot.Coverage()
- self.Coverage.Format = 1
self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0)
self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0)
@@ -763,8 +779,13 @@ def merge(merger, self, lst):
if not sts:
continue
if sts[0].__class__.__name__.startswith('Extension'):
- assert allEqual([st.__class__ for st in sts])
- assert allEqual([st.ExtensionLookupType for st in sts])
+ if not allEqual([st.__class__ for st in sts]):
+ raise InconsistentExtensions(self,
+ expected="Extension",
+ got=[st.__class__.__name__ for st in sts]
+ )
+ if not allEqual([st.ExtensionLookupType for st in sts]):
+ raise InconsistentExtensions(self)
l.LookupType = sts[0].ExtensionLookupType
new_sts = [st.ExtSubTable for st in sts]
del sts[:]
@@ -992,7 +1013,8 @@ class VariationMerger(AligningMerger):
masterModel = None
if None in lst:
if allNone(lst):
- assert out is None, (out, lst)
+ if out is not None:
+ raise FoundANone(self, got=lst)
return
masterModel = self.model
model, lst = masterModel.getSubModel(lst)
@@ -1010,9 +1032,19 @@ def buildVarDevTable(store_builder, master_values):
base, varIdx = store_builder.storeMasters(master_values)
return base, builder.buildVarDevTable(varIdx)
+@VariationMerger.merger(ot.BaseCoord)
+def merge(merger, self, lst):
+ if self.Format != 1:
+ raise UnsupportedFormat(self, subtable="a baseline coordinate")
+ self.Coordinate, DeviceTable = buildVarDevTable(merger.store_builder, [a.Coordinate for a in lst])
+ if DeviceTable:
+ self.Format = 3
+ self.DeviceTable = DeviceTable
+
@VariationMerger.merger(ot.CaretValue)
def merge(merger, self, lst):
- assert self.Format == 1
+ if self.Format != 1:
+ raise UnsupportedFormat(self, subtable="a caret")
self.Coordinate, DeviceTable = buildVarDevTable(merger.store_builder, [a.Coordinate for a in lst])
if DeviceTable:
self.Format = 3
@@ -1020,7 +1052,8 @@ def merge(merger, self, lst):
@VariationMerger.merger(ot.Anchor)
def merge(merger, self, lst):
- assert self.Format == 1
+ if self.Format != 1:
+ raise UnsupportedFormat(self, subtable="an anchor")
self.XCoordinate, XDeviceTable = buildVarDevTable(merger.store_builder, [a.XCoordinate for a in lst])
self.YCoordinate, YDeviceTable = buildVarDevTable(merger.store_builder, [a.YCoordinate for a in lst])
if XDeviceTable or YDeviceTable:
diff --git a/Lib/fontTools/varLib/models.py b/Lib/fontTools/varLib/models.py
index 9d969d73..9296deda 100644
--- a/Lib/fontTools/varLib/models.py
+++ b/Lib/fontTools/varLib/models.py
@@ -1,12 +1,13 @@
"""Variation fonts interpolation models."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
__all__ = ['nonNone', 'allNone', 'allEqual', 'allEqualTo', 'subList',
'normalizeValue', 'normalizeLocation',
'supportScalar',
'VariationModel']
+from fontTools.misc.roundTools import noRound
+from .errors import VariationModelError
+
def nonNone(lst):
return [l for l in lst if l is not None]
@@ -45,7 +46,11 @@ def normalizeValue(v, triple):
0.5
"""
lower, default, upper = triple
- assert lower <= default <= upper, "invalid axis values: %3.3f, %3.3f %3.3f"%(lower, default, upper)
+ if not (lower <= default <= upper):
+ raise ValueError(
+ f"Invalid axis values, must be minimum, default, maximum: "
+ f"{lower:3.3f}, {default:3.3f}, {upper:3.3f}"
+ )
v = max(min(v, upper), lower)
if v == default:
v = 0.
@@ -139,7 +144,7 @@ def supportScalar(location, support, ot=True):
continue
if v <= lower or upper <= v:
scalar = 0.
- break;
+ break
if v < peak:
scalar *= (v - lower) / (peak - lower)
else: # v > peak
@@ -194,7 +199,7 @@ class VariationModel(object):
def __init__(self, locations, axisOrder=None):
if len(set(tuple(sorted(l.items())) for l in locations)) != len(locations):
- raise ValueError("locations must be unique")
+ raise VariationModelError("Locations must be unique.")
self.origLocations = locations
self.axisOrder = axisOrder if axisOrder is not None else []
@@ -222,7 +227,8 @@ class VariationModel(object):
@staticmethod
def getMasterLocationsSortKeyFunc(locations, axisOrder=[]):
- assert {} in locations, "Base master not found."
+ if {} not in locations:
+ raise VariationModelError("Base master not found.")
axisPoints = {}
for loc in locations:
if len(loc) != 1:
@@ -241,7 +247,11 @@ class VariationModel(object):
return -1 if v < 0 else +1 if v > 0 else 0
def key(loc):
rank = len(loc)
- onPointAxes = [axis for axis,value in loc.items() if value in axisPoints[axis]]
+ onPointAxes = [
+ axis for axis, value in loc.items()
+ if axis in axisPoints
+ and value in axisPoints[axis]
+ ]
orderedAxes = [axis for axis in axisOrder if axis in loc]
orderedAxes.extend([axis for axis in sorted(loc.keys()) if axis not in axisOrder])
return (
@@ -272,34 +282,18 @@ class VariationModel(object):
def _computeMasterSupports(self, axisPoints):
supports = []
- deltaWeights = []
- locations = self.locations
- # Compute min/max across each axis, use it as total range.
- # TODO Take this as input from outside?
- minV = {}
- maxV = {}
- for l in locations:
- for k,v in l.items():
- minV[k] = min(v, minV.get(k, v))
- maxV[k] = max(v, maxV.get(k, v))
- for i,loc in enumerate(locations):
- box = {}
- for axis,locV in loc.items():
- if locV > 0:
- box[axis] = (0, locV, maxV[axis])
- else:
- box[axis] = (minV[axis], locV, 0)
-
- locAxes = set(loc.keys())
+ regions = self._locationsToRegions()
+ for i,region in enumerate(regions):
+ locAxes = set(region.keys())
# Walk over previous masters now
- for j,m in enumerate(locations[:i]):
+ for j,prev_region in enumerate(regions[:i]):
# Master with extra axes do not participte
- if not set(m.keys()).issubset(locAxes):
+ if not set(prev_region.keys()).issubset(locAxes):
continue
# If it's NOT in the current box, it does not participate
relevant = True
- for axis, (lower,peak,upper) in box.items():
- if axis not in m or not (m[axis] == peak or lower < m[axis] < upper):
+ for axis, (lower,peak,upper) in region.items():
+ if axis not in prev_region or not (prev_region[axis][1] == peak or lower < prev_region[axis][1] < upper):
relevant = False
break
if not relevant:
@@ -314,10 +308,10 @@ class VariationModel(object):
bestAxes = {}
bestRatio = -1
- for axis in m.keys():
- val = m[axis]
- assert axis in box
- lower,locV,upper = box[axis]
+ for axis in prev_region.keys():
+ val = prev_region[axis][1]
+ assert axis in region
+ lower,locV,upper = region[axis]
newLower, newUpper = lower, upper
if val < locV:
newLower = val
@@ -335,21 +329,46 @@ class VariationModel(object):
bestAxes[axis] = (newLower, locV, newUpper)
for axis,triple in bestAxes.items ():
- box[axis] = triple
- supports.append(box)
+ region[axis] = triple
+ supports.append(region)
+ self.supports = supports
+ self._computeDeltaWeights()
+ def _locationsToRegions(self):
+ locations = self.locations
+ # Compute min/max across each axis, use it as total range.
+ # TODO Take this as input from outside?
+ minV = {}
+ maxV = {}
+ for l in locations:
+ for k,v in l.items():
+ minV[k] = min(v, minV.get(k, v))
+ maxV[k] = max(v, maxV.get(k, v))
+
+ regions = []
+ for i,loc in enumerate(locations):
+ region = {}
+ for axis,locV in loc.items():
+ if locV > 0:
+ region[axis] = (0, locV, maxV[axis])
+ else:
+ region[axis] = (minV[axis], locV, 0)
+ regions.append(region)
+ return regions
+
+ def _computeDeltaWeights(self):
+ deltaWeights = []
+ for i,loc in enumerate(self.locations):
deltaWeight = {}
# Walk over previous masters now, populate deltaWeight
- for j,m in enumerate(locations[:i]):
- scalar = supportScalar(loc, supports[j])
+ for j,m in enumerate(self.locations[:i]):
+ scalar = supportScalar(loc, self.supports[j])
if scalar:
deltaWeight[j] = scalar
deltaWeights.append(deltaWeight)
-
- self.supports = supports
self.deltaWeights = deltaWeights
- def getDeltas(self, masterValues):
+ def getDeltas(self, masterValues, *, round=noRound):
assert len(masterValues) == len(self.deltaWeights)
mapping = self.reverseMapping
out = []
@@ -357,12 +376,12 @@ class VariationModel(object):
delta = masterValues[mapping[i]]
for j,weight in weights.items():
delta -= out[j] * weight
- out.append(delta)
+ out.append(round(delta))
return out
- def getDeltasAndSupports(self, items):
+ def getDeltasAndSupports(self, items, *, round=noRound):
model, items = self.getSubModel(items)
- return model.getDeltas(items), model.supports
+ return model.getDeltas(items, round=round), model.supports
def getScalars(self, loc):
return [supportScalar(loc, support) for support in self.supports]
@@ -371,7 +390,7 @@ class VariationModel(object):
def interpolateFromDeltasAndScalars(deltas, scalars):
v = None
assert len(deltas) == len(scalars)
- for i,(delta,scalar) in enumerate(zip(deltas, scalars)):
+ for delta, scalar in zip(deltas, scalars):
if not scalar: continue
contribution = delta * scalar
if v is None:
@@ -384,12 +403,12 @@ class VariationModel(object):
scalars = self.getScalars(loc)
return self.interpolateFromDeltasAndScalars(deltas, scalars)
- def interpolateFromMasters(self, loc, masterValues):
- deltas = self.getDeltas(masterValues)
+ def interpolateFromMasters(self, loc, masterValues, *, round=noRound):
+ deltas = self.getDeltas(masterValues, round=round)
return self.interpolateFromDeltas(loc, deltas)
- def interpolateFromMastersAndScalars(self, masterValues, scalars):
- deltas = self.getDeltas(masterValues)
+ def interpolateFromMastersAndScalars(self, masterValues, scalars, *, round=noRound):
+ deltas = self.getDeltas(masterValues, round=round)
return self.interpolateFromDeltasAndScalars(deltas, scalars)
@@ -413,26 +432,32 @@ def piecewiseLinearMap(v, mapping):
return va + (vb - va) * (v - a) / (b - a)
-def main(args):
+def main(args=None):
+ """Normalize locations on a given designspace"""
from fontTools import configLogger
+ import argparse
- args = args[1:]
+ parser = argparse.ArgumentParser(
+ "fonttools varLib.models",
+ description=main.__doc__,
+ )
+ parser.add_argument('--loglevel', metavar='LEVEL', default="INFO",
+ help="Logging level (defaults to INFO)")
- # TODO: allow user to configure logging via command-line options
- configLogger(level="INFO")
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument('-d', '--designspace',metavar="DESIGNSPACE",type=str)
+ group.add_argument('-l', '--locations', metavar='LOCATION', nargs='+',
+ help="Master locations as comma-separate coordinates. One must be all zeros.")
- if len(args) < 1:
- print("usage: fonttools varLib.models source.designspace", file=sys.stderr)
- print(" or")
- print("usage: fonttools varLib.models location1 location2 ...", file=sys.stderr)
- sys.exit(1)
+ args = parser.parse_args(args)
+ configLogger(level=args.loglevel)
from pprint import pprint
- if len(args) == 1 and args[0].endswith('.designspace'):
+ if args.designspace:
from fontTools.designspaceLib import DesignSpaceDocument
doc = DesignSpaceDocument()
- doc.read(args[0])
+ doc.read(args.designspace)
locs = [s.location for s in doc.sources]
print("Original locations:")
pprint(locs)
@@ -442,7 +467,7 @@ def main(args):
pprint(locs)
else:
axes = [chr(c) for c in range(ord('A'), ord('Z')+1)]
- locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args]
+ locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args.locations]
model = VariationModel(locs)
print("Sorted locations:")
@@ -454,6 +479,6 @@ if __name__ == "__main__":
import doctest, sys
if len(sys.argv) > 1:
- sys.exit(main(sys.argv))
+ sys.exit(main())
sys.exit(doctest.testmod().failed)
diff --git a/Lib/fontTools/varLib/mutator.py b/Lib/fontTools/varLib/mutator.py
index aba32716..02ce4422 100644
--- a/Lib/fontTools/varLib/mutator.py
+++ b/Lib/fontTools/varLib/mutator.py
@@ -3,9 +3,8 @@ Instantiate a variation font. Run, eg:
$ fonttools varLib.mutator ./NotoSansArabic-VF.ttf wght=140 wdth=85
"""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import floatToFixedToFloat, otRound, floatToFixed
+from fontTools.misc.fixedTools import floatToFixedToFloat, floatToFixed
+from fontTools.misc.roundTools import otRound
from fontTools.pens.boundsPen import BoundsPen
from fontTools.ttLib import TTFont, newTable
from fontTools.ttLib.tables import ttProgram
@@ -22,6 +21,7 @@ from fontTools.varLib.iup import iup_delta
import fontTools.subset.cff
import os.path
import logging
+from io import BytesIO
log = logging.getLogger("fontTools.varlib.mutator")
@@ -139,7 +139,7 @@ def interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc):
lsb_delta = 0
else:
lsb = boundsPen.bounds[0]
- lsb_delta = entry[1] - lsb
+ lsb_delta = entry[1] - lsb
if lsb_delta or width_delta:
if width_delta:
@@ -258,7 +258,7 @@ def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
if not tableTag in varfont:
continue
table = varfont[tableTag].table
- if not hasattr(table, 'FeatureVariations'):
+ if not getattr(table, 'FeatureVariations', None):
continue
variations = table.FeatureVariations
for record in variations.FeatureVariationRecord:
@@ -346,14 +346,8 @@ def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
# Change maxp attributes as IDEF is added
if 'maxp' in varfont:
maxp = varfont['maxp']
- if hasattr(maxp, "maxInstructionDefs"):
- maxp.maxInstructionDefs += 1
- else:
- setattr(maxp, "maxInstructionDefs", 1)
- if hasattr(maxp, "maxStackElements"):
- maxp.maxStackElements = max(len(loc), maxp.maxStackElements)
- else:
- setattr(maxp, "maxInstructionDefs", len(loc))
+ setattr(maxp, "maxInstructionDefs", 1 + getattr(maxp, "maxInstructionDefs", 0))
+ setattr(maxp, "maxStackElements", max(len(loc), getattr(maxp, "maxStackElements", 0)))
if 'name' in varfont:
log.info("Pruning name table")
@@ -400,6 +394,7 @@ def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
def main(args=None):
+ """Instantiate a variation font"""
from fontTools import configLogger
import argparse
diff --git a/Lib/fontTools/varLib/mvar.py b/Lib/fontTools/varLib/mvar.py
index 92083dd7..8b1355ba 100644
--- a/Lib/fontTools/varLib/mvar.py
+++ b/Lib/fontTools/varLib/mvar.py
@@ -1,7 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
-
MVAR_ENTRIES = {
'hasc': ('OS/2', 'sTypoAscender'), # horizontal ascender
'hdsc': ('OS/2', 'sTypoDescender'), # horizontal descender
diff --git a/Lib/fontTools/varLib/plot.py b/Lib/fontTools/varLib/plot.py
index 6b134160..811559fa 100644
--- a/Lib/fontTools/varLib/plot.py
+++ b/Lib/fontTools/varLib/plot.py
@@ -1,11 +1,9 @@
"""Visualize DesignSpaceDocument and resulting VariationModel."""
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.varLib.models import VariationModel, supportScalar
from fontTools.designspaceLib import DesignSpaceDocument
-from mpl_toolkits.mplot3d import axes3d
from matplotlib import pyplot
+from mpl_toolkits.mplot3d import axes3d
from itertools import cycle
import math
import logging
@@ -70,10 +68,10 @@ def plotLocations(locations, fig, names=None, **kwargs):
def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs):
+ subplot = fig.add_subplot(111)
for i, (support, color, name) in enumerate(
zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))
):
- subplot = fig.add_subplot(rows, cols, i + 1)
if name is not None:
subplot.set_title(name)
subplot.set_xlabel(axis)
@@ -93,10 +91,10 @@ def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs):
def _plotLocations3D(model, axes, fig, rows, cols, names, **kwargs):
ax1, ax2 = axes
+ axis3D = fig.add_subplot(111, projection='3d')
for i, (support, color, name) in enumerate(
zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))
):
- axis3D = fig.add_subplot(rows, cols, i + 1, projection='3d')
if name is not None:
axis3D.set_title(name)
axis3D.set_xlabel(ax1)
diff --git a/Lib/fontTools/varLib/varStore.py b/Lib/fontTools/varLib/varStore.py
index d9d48a51..8a382df0 100644
--- a/Lib/fontTools/varLib/varStore.py
+++ b/Lib/fontTools/varLib/varStore.py
@@ -1,6 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import noRound, otRound
from fontTools.ttLib.tables import otTables as ot
from fontTools.varLib.models import supportScalar
from fontTools.varLib.builder import (buildVarRegionList, buildVarStore,
@@ -70,7 +68,7 @@ class OnlineVarStoreBuilder(object):
self._outer = varDataIdx
self._data = self._store.VarData[varDataIdx]
self._cache = self._varDataCaches[key]
- if len(self._data.Item) == 0xFFF:
+ if len(self._data.Item) == 0xFFFF:
# This is full. Need new one.
varDataIdx = None
@@ -85,15 +83,12 @@ class OnlineVarStoreBuilder(object):
def storeMasters(self, master_values):
- deltas = self._model.getDeltas(master_values)
- base = otRound(deltas.pop(0))
- return base, self.storeDeltas(deltas)
-
- def storeDeltas(self, deltas):
- # Pity that this exists here, since VarData_addItem
- # does the same. But to look into our cache, it's
- # good to adjust deltas here as well...
- deltas = [otRound(d) for d in deltas]
+ deltas = self._model.getDeltas(master_values, round=round)
+ base = deltas.pop(0)
+ return base, self.storeDeltas(deltas, round=noRound)
+
+ def storeDeltas(self, deltas, *, round=round):
+ deltas = [round(d) for d in deltas]
if len(deltas) == len(self._supports) + 1:
deltas = tuple(deltas[1:])
else:
@@ -111,14 +106,14 @@ class OnlineVarStoreBuilder(object):
# Full array. Start new one.
self._add_VarData()
return self.storeDeltas(deltas)
- self._data.addItem(deltas)
+ self._data.addItem(deltas, round=noRound)
varIdx = (self._outer << 16) + inner
self._cache[deltas] = varIdx
return varIdx
-def VarData_addItem(self, deltas):
- deltas = [otRound(d) for d in deltas]
+def VarData_addItem(self, deltas, *, round=round):
+ deltas = [round(d) for d in deltas]
countUs = self.VarRegionCount
countThem = len(deltas)
@@ -547,12 +542,13 @@ ot.VarStore.optimize = VarStore_optimize
def main(args=None):
+ """Optimize a font's GDEF variation store"""
from argparse import ArgumentParser
from fontTools import configLogger
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.otBase import OTTableWriter
- parser = ArgumentParser(prog='varLib.varStore')
+ parser = ArgumentParser(prog='varLib.varStore', description= main.__doc__)
parser.add_argument('fontfile')
parser.add_argument('outfile', nargs='?')
options = parser.parse_args(args)
diff --git a/Lib/fontTools/voltLib/ast.py b/Lib/fontTools/voltLib/ast.py
index 1ce47819..3a1f4a07 100644
--- a/Lib/fontTools/voltLib/ast.py
+++ b/Lib/fontTools/voltLib/ast.py
@@ -1,48 +1,58 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
from fontTools.voltLib.error import VoltLibError
-
-
-class Statement(object):
+from typing import NamedTuple
+
+
+class Pos(NamedTuple):
+ adv: int
+ dx: int
+ dy: int
+ adv_adjust_by: dict
+ dx_adjust_by: dict
+ dy_adjust_by: dict
+
+ def __str__(self):
+ res = ' POS'
+ for attr in ('adv', 'dx', 'dy'):
+ value = getattr(self, attr)
+ if value is not None:
+ res += f' {attr.upper()} {value}'
+ adjust_by = getattr(self, f'{attr}_adjust_by', {})
+ for size, adjustment in adjust_by.items():
+ res += f' ADJUST_BY {adjustment} AT {size}'
+ res += ' END_POS'
+ return res
+
+
+class Element(object):
def __init__(self, location=None):
self.location = location
def build(self, builder):
pass
+ def __str__(self):
+ raise NotImplementedError
-class Expression(object):
- def __init__(self, location=None):
- self.location = location
- def build(self, builder):
- pass
+class Statement(Element):
+ pass
-class Block(Statement):
- def __init__(self, location=None):
- Statement.__init__(self, location)
+class Expression(Element):
+ pass
+
+
+class VoltFile(Statement):
+ def __init__(self):
+ Statement.__init__(self, location=None)
self.statements = []
def build(self, builder):
for s in self.statements:
s.build(builder)
-
-class VoltFile(Block):
- def __init__(self):
- Block.__init__(self, location=None)
-
-
-class LookupBlock(Block):
- def __init__(self, name, location=None):
- Block.__init__(self, location)
- self.name = name
-
- def build(self, builder):
- builder.start_lookup_block(self.location, self.name)
- Block.build(self, builder)
- builder.end_lookup_block()
+ def __str__(self):
+ return '\n' + '\n'.join(str(s) for s in self.statements) + ' END\n'
class GlyphDefinition(Statement):
@@ -54,6 +64,21 @@ class GlyphDefinition(Statement):
self.type = gtype
self.components = components
+ def __str__(self):
+ res = f'DEF_GLYPH "{self.name}" ID {self.id}'
+ if self.unicode is not None:
+ if len(self.unicode) > 1:
+ unicodes = ','.join(f'U+{u:04X}' for u in self.unicode)
+ res += f' UNICODEVALUES "{unicodes}"'
+ else:
+ res += f' UNICODE {self.unicode[0]}'
+ if self.type is not None:
+ res += f' TYPE {self.type}'
+ if self.components is not None:
+ res += f' COMPONENTS {self.components}'
+ res += ' END_GLYPH'
+ return res
+
class GroupDefinition(Statement):
def __init__(self, name, enum, location=None):
@@ -75,6 +100,10 @@ class GroupDefinition(Statement):
self.glyphs_ = self.enum.glyphSet(groups)
return self.glyphs_
+ def __str__(self):
+ enum = self.enum and str(self.enum) or ''
+ return f'DEF_GROUP "{self.name}"\n{enum}\nEND_GROUP'
+
class GlyphName(Expression):
"""A single glyph name, such as cedilla."""
@@ -85,6 +114,9 @@ class GlyphName(Expression):
def glyphSet(self):
return (self.glyph,)
+ def __str__(self):
+ return f' GLYPH "{self.glyph}"'
+
class Enum(Expression):
"""An enum"""
@@ -105,6 +137,10 @@ class Enum(Expression):
glyphs.extend(element.glyphSet())
return tuple(glyphs)
+ def __str__(self):
+ enum = ''.join(str(e) for e in self.enum)
+ return f' ENUM{enum} END_ENUM'
+
class GroupName(Expression):
"""A glyph group"""
@@ -123,6 +159,9 @@ class GroupName(Expression):
'Group "%s" is used but undefined.' % (self.group),
self.location)
+ def __str__(self):
+ return f' GROUP "{self.group}"'
+
class Range(Expression):
"""A glyph range"""
@@ -135,6 +174,9 @@ class Range(Expression):
def glyphSet(self):
return tuple(self.parser.glyph_range(self.start, self.end))
+ def __str__(self):
+ return f' RANGE "{self.start}" TO "{self.end}"'
+
class ScriptDefinition(Statement):
def __init__(self, name, tag, langs, location=None):
@@ -143,6 +185,16 @@ class ScriptDefinition(Statement):
self.tag = tag
self.langs = langs
+ def __str__(self):
+ res = 'DEF_SCRIPT'
+ if self.name is not None:
+ res += f' NAME "{self.name}"'
+ res += f' TAG "{self.tag}"\n\n'
+ for lang in self.langs:
+ res += f'{lang}'
+ res += 'END_SCRIPT'
+ return res
+
class LangSysDefinition(Statement):
def __init__(self, name, tag, features, location=None):
@@ -151,6 +203,16 @@ class LangSysDefinition(Statement):
self.tag = tag
self.features = features
+ def __str__(self):
+ res = 'DEF_LANGSYS'
+ if self.name is not None:
+ res += f' NAME "{self.name}"'
+ res += f' TAG "{self.tag}"\n\n'
+ for feature in self.features:
+ res += f'{feature}'
+ res += 'END_LANGSYS\n'
+ return res
+
class FeatureDefinition(Statement):
def __init__(self, name, tag, lookups, location=None):
@@ -159,6 +221,12 @@ class FeatureDefinition(Statement):
self.tag = tag
self.lookups = lookups
+ def __str__(self):
+ res = f'DEF_FEATURE NAME "{self.name}" TAG "{self.tag}"\n'
+ res += ' ' + ' '.join(f'LOOKUP "{l}"' for l in self.lookups) + '\n'
+ res += 'END_FEATURE\n'
+ return res
+
class LookupDefinition(Statement):
def __init__(self, name, process_base, process_marks, mark_glyph_set,
@@ -176,12 +244,51 @@ class LookupDefinition(Statement):
self.sub = sub
self.pos = pos
+ def __str__(self):
+ res = f'DEF_LOOKUP "{self.name}"'
+ res += f' {self.process_base and "PROCESS_BASE" or "SKIP_BASE"}'
+ if self.process_marks:
+ res += ' PROCESS_MARKS '
+ if self.mark_glyph_set:
+ res += f'MARK_GLYPH_SET "{self.mark_glyph_set}"'
+ elif isinstance(self.process_marks, str):
+ res += f'"{self.process_marks}"'
+ else:
+ res += 'ALL'
+ else:
+ res += ' SKIP_MARKS'
+ if self.direction is not None:
+ res += f' DIRECTION {self.direction}'
+ if self.reversal:
+ res += ' REVERSAL'
+ if self.comments is not None:
+ comments = self.comments.replace('\n', r'\n')
+ res += f'\nCOMMENTS "{comments}"'
+ if self.context:
+ res += '\n' + '\n'.join(str(c) for c in self.context)
+ else:
+ res += '\nIN_CONTEXT\nEND_CONTEXT'
+ if self.sub:
+ res += f'\n{self.sub}'
+ if self.pos:
+ res += f'\n{self.pos}'
+ return res
+
class SubstitutionDefinition(Statement):
def __init__(self, mapping, location=None):
Statement.__init__(self, location)
self.mapping = mapping
+ def __str__(self):
+ res = 'AS_SUBSTITUTION\n'
+ for src, dst in self.mapping.items():
+ src = ''.join(str(s) for s in src)
+ dst = ''.join(str(d) for d in dst)
+ res += f'SUB{src}\nWITH{dst}\nEND_SUB\n'
+ res += 'END_SUBSTITUTION'
+ return res
+
class SubstitutionSingleDefinition(SubstitutionDefinition):
pass
@@ -205,6 +312,15 @@ class PositionAttachDefinition(Statement):
self.coverage = coverage
self.coverage_to = coverage_to
+ def __str__(self):
+ coverage = ''.join(str(c) for c in self.coverage)
+ res = f'AS_POSITION\nATTACH{coverage}\nTO'
+ for coverage, anchor in self.coverage_to:
+ coverage = ''.join(str(c) for c in coverage)
+ res += f'{coverage} AT ANCHOR "{anchor}"'
+ res += '\nEND_ATTACH\nEND_POSITION'
+ return res
+
class PositionAttachCursiveDefinition(Statement):
def __init__(self, coverages_exit, coverages_enter, location=None):
@@ -212,6 +328,17 @@ class PositionAttachCursiveDefinition(Statement):
self.coverages_exit = coverages_exit
self.coverages_enter = coverages_enter
+ def __str__(self):
+ res = 'AS_POSITION\nATTACH_CURSIVE'
+ for coverage in self.coverages_exit:
+ coverage = ''.join(str(c) for c in coverage)
+ res += f'\nEXIT {coverage}'
+ for coverage in self.coverages_enter:
+ coverage = ''.join(str(c) for c in coverage)
+ res += f'\nENTER {coverage}'
+ res += '\nEND_ATTACH\nEND_POSITION'
+ return res
+
class PositionAdjustPairDefinition(Statement):
def __init__(self, coverages_1, coverages_2, adjust_pair, location=None):
@@ -220,12 +347,36 @@ class PositionAdjustPairDefinition(Statement):
self.coverages_2 = coverages_2
self.adjust_pair = adjust_pair
+ def __str__(self):
+ res = 'AS_POSITION\nADJUST_PAIR\n'
+ for coverage in self.coverages_1:
+ coverage = ' '.join(str(c) for c in coverage)
+ res += f' FIRST {coverage}'
+ res += '\n'
+ for coverage in self.coverages_2:
+ coverage = ' '.join(str(c) for c in coverage)
+ res += f' SECOND {coverage}'
+ res += '\n'
+ for (id_1, id_2), (pos_1, pos_2) in self.adjust_pair.items():
+ res += f' {id_1} {id_2} BY{pos_1}{pos_2}\n'
+ res += '\nEND_ADJUST\nEND_POSITION'
+ return res
+
class PositionAdjustSingleDefinition(Statement):
def __init__(self, adjust_single, location=None):
Statement.__init__(self, location)
self.adjust_single = adjust_single
+ def __str__(self):
+ res = 'AS_POSITION\nADJUST_SINGLE'
+ for coverage, pos in self.adjust_single:
+ coverage = ''.join(str(c) for c in coverage)
+ res += f'{coverage} BY{pos}'
+ res += '\nEND_ADJUST\nEND_POSITION'
+ return res
+
+
class ContextDefinition(Statement):
def __init__(self, ex_or_in, left=None, right=None, location=None):
@@ -234,6 +385,17 @@ class ContextDefinition(Statement):
self.left = left if left is not None else []
self.right = right if right is not None else []
+ def __str__(self):
+ res = self.ex_or_in + '\n'
+ for coverage in self.left:
+ coverage = ''.join(str(c) for c in coverage)
+ res += f' LEFT{coverage}\n'
+ for coverage in self.right:
+ coverage = ''.join(str(c) for c in coverage)
+ res += f' RIGHT{coverage}\n'
+ res += 'END_CONTEXT'
+ return res
+
class AnchorDefinition(Statement):
def __init__(self, name, gid, glyph_name, component, locked,
@@ -246,9 +408,26 @@ class AnchorDefinition(Statement):
self.locked = locked
self.pos = pos
+ def __str__(self):
+ locked = self.locked and ' LOCKED' or ''
+ return (f'DEF_ANCHOR "{self.name}"'
+ f' ON {self.gid}'
+ f' GLYPH {self.glyph_name}'
+ f' COMPONENT {self.component}'
+ f'{locked}'
+ f' AT {self.pos} END_ANCHOR')
+
class SettingDefinition(Statement):
def __init__(self, name, value, location=None):
Statement.__init__(self, location)
self.name = name
self.value = value
+
+ def __str__(self):
+ if self.value is True:
+ return f'{self.name}'
+ if isinstance(self.value, (tuple, list)):
+ value = " ".join(str(v) for v in self.value)
+ return f'{self.name} {value}'
+ return f'{self.name} {self.value}'
diff --git a/Lib/fontTools/voltLib/error.py b/Lib/fontTools/voltLib/error.py
index f9900ad3..a905de1e 100644
--- a/Lib/fontTools/voltLib/error.py
+++ b/Lib/fontTools/voltLib/error.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
class VoltLibError(Exception):
diff --git a/Lib/fontTools/voltLib/lexer.py b/Lib/fontTools/voltLib/lexer.py
index 53102b99..bc982a7a 100644
--- a/Lib/fontTools/voltLib/lexer.py
+++ b/Lib/fontTools/voltLib/lexer.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
from fontTools.voltLib.error import VoltLibError
class Lexer(object):
diff --git a/Lib/fontTools/voltLib/parser.py b/Lib/fontTools/voltLib/parser.py
index df035aa6..0e68d539 100644
--- a/Lib/fontTools/voltLib/parser.py
+++ b/Lib/fontTools/voltLib/parser.py
@@ -1,6 +1,3 @@
-from __future__ import (
- print_function, division, absolute_import, unicode_literals)
-from collections import OrderedDict
import fontTools.voltLib.ast as ast
from fontTools.voltLib.lexer import Lexer
from fontTools.voltLib.error import VoltLibError
@@ -15,9 +12,10 @@ PARSE_FUNCS = {
"GRID_PPEM": "parse_ppem_",
"PRESENTATION_PPEM": "parse_ppem_",
"PPOSITIONING_PPEM": "parse_ppem_",
- "COMPILER_USEEXTENSIONLOOKUPS": "parse_compiler_flag_",
- "COMPILER_USEPAIRPOSFORMAT2": "parse_compiler_flag_",
+ "COMPILER_USEEXTENSIONLOOKUPS": "parse_noarg_option_",
+ "COMPILER_USEPAIRPOSFORMAT2": "parse_noarg_option_",
"CMAP_FORMAT": "parse_cmap_format",
+ "DO_NOT_TOUCH_CMAP": "parse_noarg_option_",
}
@@ -217,13 +215,16 @@ class Parser(object):
if self.next_token_ == "MARK_GLYPH_SET":
self.advance_lexer_()
mark_glyph_set = self.expect_string_()
- elif self.next_token_type_ == Lexer.STRING:
- process_marks = self.expect_string_()
elif self.next_token_ == "ALL":
self.advance_lexer_()
+ elif self.next_token_ == "NONE":
+ self.advance_lexer_()
+ process_marks = False
+ elif self.next_token_type_ == Lexer.STRING:
+ process_marks = self.expect_string_()
else:
raise VoltLibError(
- "Expected ALL, MARK_GLYPH_SET or an ID. "
+ "Expected ALL, NONE, MARK_GLYPH_SET or an ID. "
"Got %s" % (self.next_token_type_),
location)
elif self.next_token_ == "SKIP_MARKS":
@@ -241,7 +242,7 @@ class Parser(object):
comments = None
if self.next_token_ == "COMMENTS":
self.expect_keyword_("COMMENTS")
- comments = self.expect_string_()
+ comments = self.expect_string_().replace(r'\n', '\n')
context = []
while self.next_token_ in ("EXCEPT_CONTEXT", "IN_CONTEXT"):
context = self.parse_context_()
@@ -311,7 +312,7 @@ class Parser(object):
raise VoltLibError(
"Invalid substitution type",
location)
- mapping = OrderedDict(zip(tuple(src), tuple(dest)))
+ mapping = dict(zip(tuple(src), tuple(dest)))
if max_src == 1 and max_dest == 1:
if reversal:
sub = ast.SubstitutionReverseChainingSingleDefinition(
@@ -493,7 +494,7 @@ class Parser(object):
adjustment, size = self.parse_adjust_by_()
dy_adjust_by[size] = adjustment
self.expect_keyword_("END_POS")
- return (adv, dx, dy, adv_adjust_by, dx_adjust_by, dy_adjust_by)
+ return ast.Pos(adv, dx, dy, adv_adjust_by, dx_adjust_by, dy_adjust_by)
def parse_unicode_values_(self):
location = self.cur_token_location_
@@ -549,11 +550,11 @@ class Parser(object):
setting = ast.SettingDefinition(ppem_name, value, location=location)
return setting
- def parse_compiler_flag_(self):
+ def parse_noarg_option_(self):
location = self.cur_token_location_
- flag_name = self.cur_token_
+ name = self.cur_token_
value = True
- setting = ast.SettingDefinition(flag_name, value, location=location)
+ setting = ast.SettingDefinition(name, value, location=location)
return setting
def parse_cmap_format(self):
@@ -631,10 +632,10 @@ class SymbolTable(object):
class OrderedSymbolTable(SymbolTable):
def __init__(self):
- self.scopes_ = [OrderedDict()]
+ self.scopes_ = [{}]
def enter_scope(self):
- self.scopes_.append(OrderedDict())
+ self.scopes_.append({})
def resolve(self, name, case_insensitive=False):
SymbolTable.resolve(self, name, case_insensitive=case_insensitive)
diff --git a/Lib/fonttools.egg-info/PKG-INFO b/Lib/fonttools.egg-info/PKG-INFO
deleted file mode 100644
index 29e94678..00000000
--- a/Lib/fonttools.egg-info/PKG-INFO
+++ /dev/null
@@ -1,1823 +0,0 @@
-Metadata-Version: 2.1
-Name: fonttools
-Version: 3.44.0
-Summary: Tools to manipulate font files
-Home-page: http://github.com/fonttools/fonttools
-Author: Just van Rossum
-Author-email: just@letterror.com
-Maintainer: Behdad Esfahbod
-Maintainer-email: behdad@behdad.org
-License: MIT
-Description: |Travis Build Status| |Appveyor Build status| |Coverage Status| |PyPI| |Gitter Chat|
-
- What is this?
- ~~~~~~~~~~~~~
-
- | fontTools is a library for manipulating fonts, written in Python. The
- project includes the TTX tool, that can convert TrueType and OpenType
- fonts to and from an XML text format, which is also called TTX. It
- supports TrueType, OpenType, AFM and to an extent Type 1 and some
- Mac-specific formats. The project has an `MIT open-source
- licence <LICENSE>`__.
- | Among other things this means you can use it free of charge.
-
- Installation
- ~~~~~~~~~~~~
-
- FontTools requires `Python <http://www.python.org/download/>`__ 2.7, 3.4
- or later.
-
- **NOTE** From August 2019, until no later than January 1 2020, the support
- for *Python 2.7* will be limited to only critical bug fixes, and no new features
- will be added to the ``py27`` branch. The upcoming FontTools 4.x series will require
- *Python 3.6* or above. You can read more `here <https://python3statement.org>`__
- and `here <https://github.com/fonttools/fonttools/issues/765>`__ for the
- reasons behind this decision.
-
- The package is listed in the Python Package Index (PyPI), so you can
- install it with `pip <https://pip.pypa.io>`__:
-
- .. code:: sh
-
- pip install fonttools
-
- If you would like to contribute to its development, you can clone the
- repository from GitHub, install the package in 'editable' mode and
- modify the source code in place. We recommend creating a virtual
- environment, using `virtualenv <https://virtualenv.pypa.io>`__ or
- Python 3 `venv <https://docs.python.org/3/library/venv.html>`__ module.
-
- .. code:: sh
-
- # download the source code to 'fonttools' folder
- git clone https://github.com/fonttools/fonttools.git
- cd fonttools
-
- # create new virtual environment called e.g. 'fonttools-venv', or anything you like
- python -m virtualenv fonttools-venv
-
- # source the `activate` shell script to enter the environment (Un*x); to exit, just type `deactivate`
- . fonttools-venv/bin/activate
-
- # to activate the virtual environment in Windows `cmd.exe`, do
- fonttools-venv\Scripts\activate.bat
-
- # install in 'editable' mode
- pip install -e .
-
- TTX – From OpenType and TrueType to XML and Back
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Once installed you can use the ``ttx`` command to convert binary font
- files (``.otf``, ``.ttf``, etc) to the TTX XML format, edit them, and
- convert them back to binary format. TTX files have a .ttx file
- extension.
-
- .. code:: sh
-
- ttx /path/to/font.otf
- ttx /path/to/font.ttx
-
- The TTX application can be used in two ways, depending on what
- platform you run it on:
-
- - As a command line tool (Windows/DOS, Unix, macOS)
- - By dropping files onto the application (Windows, macOS)
-
- TTX detects what kind of files it is fed: it will output a ``.ttx`` file
- when it sees a ``.ttf`` or ``.otf``, and it will compile a ``.ttf`` or
- ``.otf`` when the input file is a ``.ttx`` file. By default, the output
- file is created in the same folder as the input file, and will have the
- same name as the input file but with a different extension. TTX will
- *never* overwrite existing files, but if necessary will append a unique
- number to the output filename (before the extension) such as
- ``Arial#1.ttf``
-
- When using TTX from the command line there are a bunch of extra options.
- These are explained in the help text, as displayed when typing
- ``ttx -h`` at the command prompt. These additional options include:
-
- - specifying the folder where the output files are created
- - specifying which tables to dump or which tables to exclude
- - merging partial ``.ttx`` files with existing ``.ttf`` or ``.otf``
- files
- - listing brief table info instead of dumping to ``.ttx``
- - splitting tables to separate ``.ttx`` files
- - disabling TrueType instruction disassembly
-
- The TTX file format
- -------------------
-
- The following tables are currently supported:
-
- .. begin table list
- .. code::
-
- BASE, CBDT, CBLC, CFF, CFF2, COLR, CPAL, DSIG, EBDT, EBLC, FFTM,
- Feat, GDEF, GMAP, GPKG, GPOS, GSUB, Glat, Gloc, HVAR, JSTF, LTSH,
- MATH, META, MVAR, OS/2, SING, STAT, SVG, Silf, Sill, TSI0, TSI1,
- TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS, TSIV, TTFA, VDMX,
- VORG, VVAR, ankr, avar, bsln, cidg, cmap, cvar, cvt, feat, fpgm,
- fvar, gasp, gcid, glyf, gvar, hdmx, head, hhea, hmtx, kern, lcar,
- loca, ltag, maxp, meta, mort, morx, name, opbd, post, prep, prop,
- sbix, trak, vhea and vmtx
- .. end table list
-
- Other tables are dumped as hexadecimal data.
-
- TrueType fonts use glyph indices (GlyphIDs) to refer to glyphs in most
- places. While this is fine in binary form, it is really hard to work
- with for humans. Therefore we use names instead.
-
- The glyph names are either extracted from the ``CFF`` table or the
- ``post`` table, or are derived from a Unicode ``cmap`` table. In the
- latter case the Adobe Glyph List is used to calculate names based on
- Unicode values. If all of these methods fail, names are invented based
- on GlyphID (eg ``glyph00142``)
-
- It is possible that different glyphs use the same name. If this happens,
- we force the names to be unique by appending ``#n`` to the name (``n``
- being an integer number.) The original names are being kept, so this has
- no influence on a "round tripped" font.
-
- Because the order in which glyphs are stored inside the binary font is
- important, we maintain an ordered list of glyph names in the font.
-
- Other Tools
- ~~~~~~~~~~~
-
- Commands for merging and subsetting fonts are also available:
-
- .. code:: sh
-
- pyftmerge
- pyftsubset
-
- fontTools Python Module
- ~~~~~~~~~~~~~~~~~~~~~~~
-
- The fontTools Python module provides a convenient way to
- programmatically edit font files.
-
- .. code:: py
-
- >>> from fontTools.ttLib import TTFont
- >>> font = TTFont('/path/to/font.ttf')
- >>> font
- <fontTools.ttLib.TTFont object at 0x10c34ed50>
- >>>
-
- A selection of sample Python programs is in the
- `Snippets <https://github.com/fonttools/fonttools/blob/master/Snippets/>`__
- directory.
-
- Optional Requirements
- ---------------------
-
- The ``fontTools`` package currently has no (required) external dependencies
- besides the modules included in the Python Standard Library.
- However, a few extra dependencies are required by some of its modules, which
- are needed to unlock optional features.
- The ``fonttools`` PyPI distribution also supports so-called "extras", i.e. a
- set of keywords that describe a group of additional dependencies, which can be
- used when installing via pip, or when specifying a requirement.
- For example:
-
- .. code:: sh
-
- pip install fonttools[ufo,lxml,woff,unicode]
-
- This command will install fonttools, as well as the optional dependencies that
- are required to unlock the extra features named "ufo", etc.
-
- - ``Lib/fontTools/misc/etree.py``
-
- The module exports a ElementTree-like API for reading/writing XML files, and
- allows to use as the backend either the built-in ``xml.etree`` module or
- `lxml <https://http://lxml.de>`__. The latter is preferred whenever present,
- as it is generally faster and more secure.
-
- *Extra:* ``lxml``
-
- - ``Lib/fontTools/ufoLib``
-
- Package for reading and writing UFO source files; it requires:
-
- * `fs <https://pypi.org/pypi/fs>`__: (aka ``pyfilesystem2``) filesystem
- abstraction layer.
-
- * `enum34 <https://pypi.org/pypi/enum34>`__: backport for the built-in ``enum``
- module (only required on Python < 3.4).
-
- *Extra:* ``ufo``
-
- - ``Lib/fontTools/ttLib/woff2.py``
-
- Module to compress/decompress WOFF 2.0 web fonts; it requires:
-
- * `brotli <https://pypi.python.org/pypi/Brotli>`__: Python bindings of
- the Brotli compression library.
-
- *Extra:* ``woff``
-
- - ``Lib/fontTools/ttLib/sfnt.py``
-
- To better compress WOFF 1.0 web fonts, the following module can be used
- instead of the built-in ``zlib`` library:
-
- * `zopfli <https://pypi.python.org/pypi/zopfli>`__: Python bindings of
- the Zopfli compression library.
-
- *Extra:* ``woff``
-
- - ``Lib/fontTools/unicode.py``
-
- To display the Unicode character names when dumping the ``cmap`` table
- with ``ttx`` we use the ``unicodedata`` module in the Standard Library.
- The version included in there varies between different Python versions.
- To use the latest available data, you can install:
-
- * `unicodedata2 <https://pypi.python.org/pypi/unicodedata2>`__:
- ``unicodedata`` backport for Python 2.7 and 3.x updated to the latest
- Unicode version 12.0. Note this is not necessary if you use Python 3.8
- as the latter already comes with an up-to-date ``unicodedata``.
-
- *Extra:* ``unicode``
-
- - ``Lib/fontTools/varLib/interpolatable.py``
-
- Module for finding wrong contour/component order between different masters.
- It requires one of the following packages in order to solve the so-called
- "minimum weight perfect matching problem in bipartite graphs", or
- the Assignment problem:
-
- * `scipy <https://pypi.python.org/pypi/scipy>`__: the Scientific Library
- for Python, which internally uses `NumPy <https://pypi.python.org/pypi/numpy>`__
- arrays and hence is very fast;
- * `munkres <https://pypi.python.org/pypi/munkres>`__: a pure-Python
- module that implements the Hungarian or Kuhn-Munkres algorithm.
-
- *Extra:* ``interpolatable``
-
- - ``Lib/fontTools/varLib/plot.py``
-
- Module for visualizing DesignSpaceDocument and resulting VariationModel.
-
- * `matplotlib <https://pypi.org/pypi/matplotlib>`__: 2D plotting library.
-
- *Extra:* ``plot``
-
- - ``Lib/fontTools/misc/symfont.py``
-
- Advanced module for symbolic font statistics analysis; it requires:
-
- * `sympy <https://pypi.python.org/pypi/sympy>`__: the Python library for
- symbolic mathematics.
-
- *Extra:* ``symfont``
-
- - ``Lib/fontTools/t1Lib.py``
-
- To get the file creator and type of Macintosh PostScript Type 1 fonts
- on Python 3 you need to install the following module, as the old ``MacOS``
- module is no longer included in Mac Python:
-
- * `xattr <https://pypi.python.org/pypi/xattr>`__: Python wrapper for
- extended filesystem attributes (macOS platform only).
-
- *Extra:* ``type1``
-
- - ``Lib/fontTools/pens/cocoaPen.py``
-
- Pen for drawing glyphs with Cocoa ``NSBezierPath``, requires:
-
- * `PyObjC <https://pypi.python.org/pypi/pyobjc>`__: the bridge between
- Python and the Objective-C runtime (macOS platform only).
-
- - ``Lib/fontTools/pens/qtPen.py``
-
- Pen for drawing glyphs with Qt's ``QPainterPath``, requires:
-
- * `PyQt5 <https://pypi.python.org/pypi/PyQt5>`__: Python bindings for
- the Qt cross platform UI and application toolkit.
-
- - ``Lib/fontTools/pens/reportLabPen.py``
-
- Pen to drawing glyphs as PNG images, requires:
-
- * `reportlab <https://pypi.python.org/pypi/reportlab>`__: Python toolkit
- for generating PDFs and graphics.
-
- Testing
- ~~~~~~~
-
- To run the test suite, you need to install `pytest <http://docs.pytest.org/en/latest/>`__.
- When you run the ``pytest`` command, the tests will run against the
- installed ``fontTools`` package, or the first one found in the
- ``PYTHONPATH``.
-
- You can also use `tox <https://tox.readthedocs.io/en/latest/>`__ to
- automatically run tests on different Python versions in isolated virtual
- environments.
-
- .. code:: sh
-
- pip install tox
- tox
-
- Note that when you run ``tox`` without arguments, the tests are executed
- for all the environments listed in tox.ini's ``envlist``. In our case,
- this includes Python 2.7 and 3.7, so for this to work the ``python2.7``
- and ``python3.7`` executables must be available in your ``PATH``.
-
- You can specify an alternative environment list via the ``-e`` option,
- or the ``TOXENV`` environment variable:
-
- .. code:: sh
-
- tox -e py27
- TOXENV="py36-cov,htmlcov" tox
-
- Development Community
- ~~~~~~~~~~~~~~~~~~~~~
-
- TTX/FontTools development is ongoing in an active community of
- developers, that includes professional developers employed at major
- software corporations and type foundries as well as hobbyists.
-
- Feature requests and bug reports are always welcome at
- https://github.com/fonttools/fonttools/issues/
-
- The best place for discussions about TTX from an end-user perspective as
- well as TTX/FontTools development is the
- https://groups.google.com/d/forum/fonttools mailing list. There is also
- a development https://groups.google.com/d/forum/fonttools-dev mailing
- list for continuous integration notifications. You can also email Behdad
- privately at behdad@behdad.org
-
- History
- ~~~~~~~
-
- The fontTools project was started by Just van Rossum in 1999, and was
- maintained as an open source project at
- http://sourceforge.net/projects/fonttools/. In 2008, Paul Wise (pabs3)
- began helping Just with stability maintenance. In 2013 Behdad Esfahbod
- began a friendly fork, thoroughly reviewing the codebase and making
- changes at https://github.com/behdad/fonttools to add new features and
- support for new font formats.
-
- Acknowledgements
- ~~~~~~~~~~~~~~~~
-
- In alphabetical order:
-
- Olivier Berten, Samyak Bhuta, Erik van Blokland, Petr van Blokland,
- Jelle Bosma, Sascha Brawer, Tom Byrer, Frédéric Coiffier, Vincent
- Connare, Dave Crossland, Simon Daniels, Peter Dekkers, Behdad Esfahbod,
- Behnam Esfahbod, Hannes Famira, Sam Fishman, Matt Fontaine, Yannis
- Haralambous, Greg Hitchcock, Jeremie Hornus, Khaled Hosny, John Hudson,
- Denis Moyogo Jacquerye, Jack Jansen, Tom Kacvinsky, Jens Kutilek,
- Antoine Leca, Werner Lemberg, Tal Leming, Peter Lofting, Cosimo Lupo,
- Masaya Nakamura, Dave Opstad, Laurence Penney, Roozbeh Pournader, Garret
- Rieger, Read Roberts, Guido van Rossum, Just van Rossum, Andreas Seidel,
- Georg Seifert, Miguel Sousa, Adam Twardoch, Adrien Tétar, Vitaly Volkov,
- Paul Wise.
-
- Copyrights
- ~~~~~~~~~~
-
- | Copyright (c) 1999-2004 Just van Rossum, LettError
- (just@letterror.com)
- | See `LICENSE <LICENSE>`__ for the full license.
-
- Copyright (c) 2000 BeOpen.com. All Rights Reserved.
-
- Copyright (c) 1995-2001 Corporation for National Research Initiatives.
- All Rights Reserved.
-
- Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam. All
- Rights Reserved.
-
- Have fun!
-
- .. |Travis Build Status| image:: https://travis-ci.org/fonttools/fonttools.svg
- :target: https://travis-ci.org/fonttools/fonttools
- .. |Appveyor Build status| image:: https://ci.appveyor.com/api/projects/status/0f7fmee9as744sl7/branch/master?svg=true
- :target: https://ci.appveyor.com/project/fonttools/fonttools/branch/master
- .. |Coverage Status| image:: https://codecov.io/gh/fonttools/fonttools/branch/master/graph/badge.svg
- :target: https://codecov.io/gh/fonttools/fonttools
- .. |PyPI| image:: https://img.shields.io/pypi/v/fonttools.svg
- :target: https://pypi.org/project/FontTools
- .. |Gitter Chat| image:: https://badges.gitter.im/fonttools-dev/Lobby.svg
- :alt: Join the chat at https://gitter.im/fonttools-dev/Lobby
- :target: https://gitter.im/fonttools-dev/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
-
- Changelog
- ~~~~~~~~~
-
- 3.44.0 (released 2019-08-02)
- ----------------------------
-
- - NOTE: This is the last scheduled release to support Python 2.7. The upcoming fonttools
- v4.x series is going to require Python 3.6 or greater.
- - [varLib] Added new ``varLib.instancer`` module for partially instantiating variable
- fonts. This extends (and will eventually replace) ``varLib.mutator`` module, as
- it allows to create not just full static instances from a variable font, but also
- "partial" or "less variable" fonts where some of the axes are dropped or
- instantiated at a particular value.
- Also available from the command-line as `fonttools varLib.instancer --help`
- (#1537, #1628).
- - [cffLib] Added support for ``FDSelect`` format 4 (#1677).
- - [subset] Added support for subsetting ``sbix`` (Apple bitmap color font) table.
- - [t1Lib] Fixed issue parsing ``eexec`` section in Type1 fonts when whitespace
- characters are interspersed among the trailing zeros (#1676).
- - [cffLib.specializer] Fixed bug in ``programToCommands`` with CFF2 charstrings (#1669).
-
- 3.43.2 (released 2019-07-10)
- ----------------------------
-
- - [featureVars] Fixed region-merging code on python3 (#1659).
- - [varLib.cff] Fixed merging of sparse PrivateDict items (#1653).
-
- 3.43.1 (released 2019-06-19)
- ----------------------------
-
- - [subset] Fixed regression when passing ``--flavor=woff2`` option with an input font
- that was already compressed as WOFF 1.0 (#1650).
-
- 3.43.0 (released 2019-06-18)
- ----------------------------
-
- - [woff2] Added support for compressing/decompressing WOFF2 fonts with non-transformed
- ``glyf`` and ``loca`` tables, as well as with transformed ``hmtx`` table.
- Removed ``Snippets/woff2_compress.py`` and ``Snippets/woff2_decompress.py`` scripts,
- and replaced them with a new console entry point ``fonttools ttLib.woff2``
- that provides two sub-commands ``compress`` and ``decompress``.
- - [varLib.cff] Fixed bug when merging CFF2 ``PrivateDicts``. The ``PrivateDict``
- data from the first region font was incorrecty used for all subsequent fonts.
- The bug would only affect variable CFF2 fonts with hinting (#1643, #1644).
- Also, fixed a merging bug when VF masters have no blends or marking glyphs (#1632,
- #1642).
- - [loggingTools] Removed unused backport of ``LastResortLogger`` class.
- - [subset] Gracefully handle partial MATH table (#1635).
- - [featureVars] Avoid duplicate references to ``rvrn`` feature record in
- ``DefaultLangSys`` tables when calling ``addFeatureVariations`` on a font that
- does not already have a ``GSUB`` table (aa8a5bc6).
- - [varLib] Fixed merging of class-based kerning. Before, the process could introduce
- rogue kerning values and variations for random classes against class zero (everything
- not otherwise classed).
- - [varLib] Fixed merging GPOS tables from master fonts with different number of
- ``SinglePos`` subtables (#1621, #1641).
- - [unicodedata] Updated Blocks, Scripts and ScriptExtensions to Unicode 12.1.
-
- 3.42.0 (released 2019-05-28)
- ----------------------------
-
- - [OS/2] Fixed sign of ``fsType``: it should be ``uint16``, not ``int16`` (#1619).
- - [subset] Skip out-of-range class values in mark attachment (#1478).
- - [fontBuilder] Add an empty ``DSIG`` table with ``setupDummyDSIG`` method (#1621).
- - [varLib.merger] Fixed bug whereby ``GDEF.GlyphClassDef`` were being dropped
- when generating instance via ``varLib.mutator`` (#1614).
- - [varLib] Added command-line options ``-v`` and ``-q`` to configure logging (#1613).
- - [subset] Update font extents in head table (#1612).
- - [subset] Make --retain-gids truncate empty glyphs after the last non-empty glyph
- (#1611).
- - [requirements] Updated ``unicodedata2`` backport for Unicode 12.0.
-
- 3.41.2 (released 2019-05-13)
- ----------------------------
-
- - [cffLib] Fixed issue when importing a ``CFF2`` variable font from XML, whereby
- the VarStore state was not propagated to PrivateDict (#1598).
- - [varLib] Don't drop ``post`` glyph names when building CFF2 variable font (#1609).
-
-
- 3.41.1 (released 2019-05-13)
- ----------------------------
-
- - [designspaceLib] Added ``loadSourceFonts`` method to load source fonts using
- custom opener function (#1606).
- - [head] Round font bounding box coordinates to integers to fix compile error
- if CFF font has float coordinates (#1604, #1605).
- - [feaLib] Don't write ``None`` in ``ast.ValueRecord.asFea()`` (#1599).
- - [subset] Fixed issue ``AssertionError`` when using ``--desubroutinize`` option
- (#1590, #1594).
- - [graphite] Fixed bug in ``Silf`` table's ``decompile`` method unmasked by
- previous typo fix (#1597). Decode languange code as UTF-8 in ``Sill`` table's
- ``decompile`` method (#1600).
-
- 3.41.0 (released 2019-04-29)
- ----------------------------
-
- - [varLib/cffLib] Added support for building ``CFF2`` variable font from sparse
- masters, or masters with more than one model (multiple ``VarStore.VarData``).
- In ``cffLib.specializer``, added support for ``CFF2`` CharStrings with
- ``blend`` operators (#1547, #1591).
- - [subset] Fixed subsetting ``HVAR`` and ``VVAR`` with ``--retain-gids`` option,
- and when advances mapping is null while sidebearings mappings are non-null
- (#1587, #1588).
- - Added ``otlLib.maxContextCalc`` module to compute ``OS/2.usMaxContext`` value.
- Calculate it automatically when compiling features with feaLib. Added option
- ``--recalc-max-context`` to ``subset`` module (#1582).
- - [otBase/otTables] Fixed ``AttributeError`` on missing OT table fields after
- importing font from TTX (#1584).
- - [graphite] Fixed typo ``Silf`` table's ``decompile`` method (#1586).
- - [otlLib] Better compress ``GPOS`` SinglePos (LookupType 1) subtables (#1539).
-
- 3.40.0 (released 2019-04-08)
- ----------------------------
-
- - [subset] Fixed error while subsetting ``VVAR`` with ``--retain-gids``
- option (#1552).
- - [designspaceLib] Use up-to-date default location in ``findDefault`` method
- (#1554).
- - [voltLib] Allow passing file-like object to Parser.
- - [arrayTools/glyf] ``calcIntBounds`` (used to compute bounding boxes of glyf
- table's glyphs) now uses ``otRound`` instead of ``round3`` (#1566).
- - [svgLib] Added support for converting more SVG shapes to path ``d`` strings
- (ellipse, line, polyline), as well as support for ``transform`` attributes.
- Only ``matrix`` transformations are currently supported (#1564, #1564).
- - [varLib] Added support for building ``VVAR`` table from ``vmtx`` and ``VORG``
- tables (#1551).
- - [fontBuilder] Enable making CFF2 fonts with ``post`` table format 2 (#1557).
- - Fixed ``DeprecationWarning`` on invalid escape sequences (#1562).
-
- 3.39.0 (released 2019-03-19)
- ----------------------------
-
- - [ttLib/glyf] Raise more specific error when encountering recursive
- component references (#1545, #1546).
- - [Doc/designspaceLib] Defined new ``public.skipExportGlyphs`` lib key (#1534,
- unified-font-object/ufo-spec#84).
- - [varLib] Use ``vmtx`` to compute vertical phantom points; or ``hhea.ascent``
- and ``head.unitsPerEM`` if ``vmtx`` is missing (#1528).
- - [gvar/cvar] Sort XML element's min/value/max attributes in TupleVariation
- toXML to improve readability of TTX dump (#1527).
- - [varLib.plot] Added support for 2D plots with only 1 variation axis (#1522).
- - [designspaceLib] Use axes maps when normalizing locations in
- DesignSpaceDocument (#1226, #1521), and when finding default source (#1535).
- - [mutator] Set ``OVERLAP_SIMPLE`` and ``OVERLAP_COMPOUND`` glyf flags by
- default in ``instantiateVariableFont``. Added ``--no-overlap`` cli option
- to disable this (#1518).
- - [subset] Fixed subsetting ``VVAR`` table (#1516, #1517).
- Fixed subsetting an ``HVAR`` table that has an ``AdvanceWidthMap`` when the
- option ``--retain-gids`` is used.
- - [feaLib] Added ``forceChained`` in MultipleSubstStatement (#1511).
- Fixed double indentation of ``subtable`` statement (#1512).
- Added support for ``subtable`` statement in more places than just PairPos
- lookups (#1520).
- Handle lookupflag 0 and lookupflag without a value (#1540).
- - [varLib] In ``load_designspace``, provide a default English name for the
- ``ital`` axis tag.
- - Remove pyftinspect because it is unmaintained and bitrotted.
-
- 3.38.0 (released 2019-02-18)
- ----------------------------
-
- - [cffLib] Fixed RecursionError when unpickling or deepcopying TTFont with
- CFF table (#1488, 649dc49).
- - [subset] Fixed AttributeError when using --desubroutinize option (#1490).
- Also, fixed desubroutinizing bug when subrs contain hints (#1499).
- - [CPAL] Make Color a subclass of namedtuple (173a0f5).
- - [feaLib] Allow hyphen in glyph class names.
- - [feaLib] Added 'tables' option to __main__.py (#1497).
- - [feaLib] Add support for special-case contextual positioning formatting
- (#1501).
- - [svgLib] Support converting SVG basic shapes (rect, circle, etc.) into
- equivalent SVG paths (#1500, #1508).
- - [Snippets] Added name-viewer.ipynb Jupyter notebook.
-
-
- 3.37.3 (released 2019-02-05)
- ----------------------------
-
- - The previous release accidentally changed several files from Unix to DOS
- line-endings. Fix that.
-
- 3.37.2 (released 2019-02-05)
- ----------------------------
-
- - [varLib] Temporarily revert the fix to ``load_masters()``, which caused a
- crash in ``interpolate_layout()`` when ``deepcopy``-ing OTFs.
-
- 3.37.1 (released 2019-02-05)
- ----------------------------
-
- - [varLib] ``load_masters()`` now actually assigns the fonts it loads to the
- source.font attributes.
- - [varLib] Fixed an MVAR table generation crash when sparse masters were
- involved.
- - [voltLib] ``parse_coverage_()`` returns a tuple instead of an ast.Enum.
- - [feaLib] A MarkClassDefinition inside a block is no longer doubly indented
- compared to the rest of the block.
-
- 3.37.0 (released 2019-01-28)
- ----------------------------
-
- - [svgLib] Added support for converting elliptical arcs to cubic bezier curves
- (#1464).
- - [py23] Added backport for ``math.isfinite``.
- - [varLib] Apply HIDDEN flag to fvar axis if designspace axis has attribute
- ``hidden=1``.
- - Fixed "DeprecationWarning: invalid escape sequence" in Python 3.7.
- - [voltLib] Fixed parsing glyph groups. Distinguish different PROCESS_MARKS.
- Accept COMPONENT glyph type.
- - [feaLib] Distinguish missing value and explicit ``<NULL>`` for PairPos2
- format A (#1459). Round-trip ``useExtension`` keyword. Implemented
- ``ValueRecord.asFea`` method.
- - [subset] Insert empty widths into hdmx when retaining gids (#1458).
-
- 3.36.0 (released 2019-01-17)
- ----------------------------
-
- - [ttx] Added ``--no-recalc-timestamp`` option to keep the original font's
- ``head.modified`` timestamp (#1455, #46).
- - [ttx/psCharStrings] Fixed issues while dumping and round-tripping CFF2 table
- with ttx (#1451, #1452, #1456).
- - [voltLib] Fixed check for duplicate anchors (#1450). Don't try to read past
- the ``END`` operator in .vtp file (#1453).
- - [varLib] Use sentinel value -0x8000 (-32768) to ignore post.underlineThickness
- and post.underlinePosition when generating MVAR deltas (#1449,
- googlei18n/ufo2ft#308).
- - [subset] Added ``--retain-gids`` option to subset font without modifying the
- current glyph indices (#1443, #1447).
- - [ufoLib] Replace deprecated calls to ``getbytes`` and ``setbytes`` with new
- equivalent ``readbytes`` and ``writebytes`` calls. ``fs`` >= 2.2 no required.
- - [varLib] Allow loading masters from TTX files as well (#1441).
-
- 3.35.2 (released 2019-01-14)
- ----------------------------
-
- - [hmtx/vmtx]: Allow to compile/decompile ``hmtx`` and ``vmtx`` tables even
- without the corresponding (required) metrics header tables, ``hhea`` and
- ``vhea`` (#1439).
- - [varLib] Added support for localized axes' ``labelname`` and named instances'
- ``stylename`` (#1438).
-
- 3.35.1 (released 2019-01-09)
- ----------------------------
-
- - [_m_a_x_p] Include ``maxComponentElements`` in ``maxp`` table's recalculation.
-
- 3.35.0 (released 2019-01-07)
- ----------------------------
-
- - [psCharStrings] In ``encodeFloat`` function, use float's "general format" with
- 8 digits of precision (i.e. ``%8g``) instead of ``str()``. This works around
- a macOS rendering issue when real numbers in CFF table are too long, and
- also makes sure that floats are encoded with the same precision in python 2.7
- and 3.x (#1430, googlei18n/ufo2ft#306).
- - [_n_a_m_e/fontBuilder] Make ``_n_a_m_e_table.addMultilingualName`` also add
- Macintosh (platformID=1) names by default. Added options to ``FontBuilder``
- ``setupNameTable`` method to optionally disable Macintosh or Windows names.
- (#1359, #1431).
- - [varLib] Make ``build`` optionally accept a ``DesignSpaceDocument`` object,
- instead of a designspace file path. The caller can now set the ``font``
- attribute of designspace's sources to a TTFont object, thus allowing to
- skip filenames manipulation altogether (#1416, #1425).
- - [sfnt] Allow SFNTReader objects to be deep-copied.
- - Require typing>=3.6.4 on py27 to fix issue with singledispatch (#1423).
- - [designspaceLib/t1Lib/macRes] Fixed some cases where pathlib.Path objects were
- not accepted (#1421).
- - [varLib] Fixed merging of multiple PairPosFormat2 subtables (#1411).
- - [varLib] The default STAT table version is now set to 1.1, to improve
- compatibility with legacy applications (#1413).
-
- 3.34.2 (released 2018-12-17)
- ----------------------------
-
- - [merge] Fixed AssertionError when none of the script tables in GPOS/GSUB have
- a DefaultLangSys record (#1408, 135a4a1).
-
- 3.34.1 (released 2018-12-17)
- ----------------------------
-
- - [varLib] Work around macOS rendering issue for composites without gvar entry (#1381).
-
- 3.34.0 (released 2018-12-14)
- ----------------------------
-
- - [varLib] Support generation of CFF2 variable fonts. ``model.reorderMasters()``
- now supports arbitrary mapping. Fix handling of overlapping ranges for feature
- variations (#1400).
- - [cffLib, subset] Code clean-up and fixing related to CFF2 support.
- - [ttLib.tables.ttProgram] Use raw strings for regex patterns (#1389).
- - [fontbuilder] Initial support for building CFF2 fonts. Set CFF's
- ``FontMatrix`` automatically from unitsPerEm.
- - [plistLib] Accept the more general ``collections.Mapping`` instead of the
- specific ``dict`` class to support custom data classes that should serialize
- to dictionaries.
-
- 3.33.0 (released 2018-11-30)
- ----------------------------
- - [subset] subsetter bug fix with variable fonts.
- - [varLib.featureVar] Improve FeatureVariations generation with many rules.
- - [varLib] Enable sparse masters when building variable fonts:
- https://github.com/fonttools/fonttools/pull/1368#issuecomment-437257368
- - [varLib.mutator] Add IDEF for GETVARIATION opcode, for handling hints in an
- instance.
- - [ttLib] Ignore the length of kern table subtable format 0
-
- 3.32.0 (released 2018-11-01)
- ----------------------------
-
- - [ufoLib] Make ``UFOWriter`` a subclass of ``UFOReader``, and use mixins
- for shared methods (#1344).
- - [featureVars] Fixed normalization error when a condition's minimum/maximum
- attributes are missing in designspace ``<rule>`` (#1366).
- - [setup.py] Added ``[plot]`` to extras, to optionally install ``matplotlib``,
- needed to use the ``fonTools.varLib.plot`` module.
- - [varLib] Take total bounding box into account when resolving model (7ee81c8).
- If multiple axes have the same range ratio, cut across both (62003f4).
- - [subset] Don't error if ``STAT`` has no ``AxisValue`` tables.
- - [fontBuilder] Added a new submodule which contains a ``FontBuilder`` wrapper
- class around ``TTFont`` that makes it easier to create a working TTF or OTF
- font from scratch with code. NOTE: the API is still experimental and may
- change in future versions.
-
- 3.31.0 (released 2018-10-21)
- ----------------------------
-
- - [ufoLib] Merged the `ufoLib <https://github.com/unified-font-objects/ufoLib>`__
- master branch into a new ``fontTools.ufoLib`` package (#1335, #1095).
- Moved ``ufoLib.pointPen`` module to ``fontTools.pens.pointPen``.
- Moved ``ufoLib.etree`` module to ``fontTools.misc.etree``.
- Moved ``ufoLib.plistlib`` module to ``fontTools.misc.plistlib``.
- To use the new ``fontTools.ufoLib`` module you need to install fonttools
- with the ``[ufo]`` extra, or you can manually install the required additional
- dependencies (cf. README.rst).
- - [morx] Support AAT action type to insert glyphs and clean up compilation
- of AAT action tables (4a1871f, 2011ccf).
- - [subset] The ``--no-hinting`` on a CFF font now also drops the optional
- hinting keys in Private dict: ``ForceBold``, ``LanguageGroup``, and
- ``ExpansionFactor`` (#1322).
- - [subset] Include nameIDs referenced by STAT table (#1327).
- - [loggingTools] Added ``msg=None`` argument to
- ``CapturingLogHandler.assertRegex`` (0245f2c).
- - [varLib.mutator] Implemented ``FeatureVariations`` instantiation (#1244).
- - [g_l_y_f] Added PointPen support to ``_TTGlyph`` objects (#1334).
-
- 3.30.0 (released 2018-09-18)
- ----------------------------
-
- - [feaLib] Skip building noop class PairPos subtables when Coverage is NULL
- (#1318).
- - [ttx] Expose the previously reserved bit flag ``OVERLAP_SIMPLE`` of
- glyf table's contour points in the TTX dump. This is used in some
- implementations to specify a non-zero fill with overlapping contours (#1316).
- - [ttLib] Added support for decompiling/compiling ``TS1C`` tables containing
- VTT sources for ``cvar`` variation table (#1310).
- - [varLib] Use ``fontTools.designspaceLib`` to read DesignSpaceDocument. The
- ``fontTools.varLib.designspace`` module is now deprecated and will be removed
- in future versions. The presence of an explicit ``axes`` element is now
- required in order to build a variable font (#1224, #1313).
- - [varLib] Implemented building GSUB FeatureVariations table from the ``rules``
- element of DesignSpace document (#1240, #713, #1314).
- - [subset] Added ``--no-layout-closure`` option to not expand the subset with
- the glyphs produced by OpenType layout features. Instead, OpenType features
- will be subset to only rules that are relevant to the otherwise-specified
- glyph set (#43, #1121).
-
- 3.29.1 (released 2018-09-10)
- ----------------------------
-
- - [feaLib] Fixed issue whereby lookups from DFLT/dflt were not included in the
- DFLT/non-dflt language systems (#1307).
- - [graphite] Fixed issue on big-endian architectures (e.g. ppc64) (#1311).
- - [subset] Added ``--layout-scripts`` option to add/exclude set of OpenType
- layout scripts that will be preserved. By default all scripts are retained
- (``'*'``) (#1303).
-
- 3.29.0 (released 2018-07-26)
- ----------------------------
-
- - [feaLib] In the OTL table builder, when the ``name`` table is excluded
- from the list of tables to be build, skip compiling ``featureNames`` blocks,
- as the records referenced in ``FeatureParams`` table don't exist (68951b7).
- - [otBase] Try ``ExtensionLookup`` if other offset-overflow methods fail
- (05f95f0).
- - [feaLib] Added support for explicit ``subtable;`` break statements in
- PairPos lookups; previously these were ignored (#1279, #1300, #1302).
- - [cffLib.specializer] Make sure the stack depth does not exceed maxstack - 1,
- so that a subroutinizer can insert subroutine calls (#1301,
- https://github.com/googlei18n/ufo2ft/issues/266).
- - [otTables] Added support for fixing offset overflow errors occurring inside
- ``MarkBasePos`` subtables (#1297).
- - [subset] Write the default output file extension based on ``--flavor`` option,
- or the value of ``TTFont.sfntVersion`` (d7ac0ad).
- - [unicodedata] Updated Blocks, Scripts and ScriptExtensions for Unicode 11
- (452c85e).
- - [xmlWriter] Added context manager to XMLWriter class to autoclose file
- descriptor on exit (#1290).
- - [psCharStrings] Optimize the charstring's bytecode by encoding as integers
- all float values that have no decimal portion (8d7774a).
- - [ttFont] Fixed missing import of ``TTLibError`` exception (#1285).
- - [feaLib] Allow any languages other than ``dflt`` under ``DFLT`` script
- (#1278, #1292).
-
- 3.28.0 (released 2018-06-19)
- ----------------------------
-
- - [featureVars] Added experimental module to build ``FeatureVariations``
- tables. Still needs to be hooked up to ``varLib.build`` (#1240).
- - [fixedTools] Added ``otRound`` to round floats to nearest integer towards
- positive Infinity. This is now used where we deal with visual data like X/Y
- coordinates, advance widths/heights, variation deltas, and similar (#1274,
- #1248).
- - [subset] Improved GSUB closure memoize algorithm.
- - [varLib.models] Fixed regression in model resolution (180124, #1269).
- - [feaLib.ast] Fixed error when converting ``SubtableStatement`` to string
- (#1275).
- - [varLib.mutator] Set ``OS/2.usWeightClass`` and ``usWidthClass``, and
- ``post.italicAngle`` based on the 'wght', 'wdth' and 'slnt' axis values
- (#1276, #1264).
- - [py23/loggingTools] Don't automatically set ``logging.lastResort`` handler
- on py27. Moved ``LastResortLogger`` to the ``loggingTools`` module (#1277).
-
- 3.27.1 (released 2018-06-11)
- ----------------------------
-
- - [ttGlyphPen] Issue a warning and skip building non-existing components
- (https://github.com/googlei18n/fontmake/issues/411).
- - [tests] Fixed issue running ttx_test.py from a tagged commit.
-
- 3.27.0 (released 2018-06-11)
- ----------------------------
-
- - [designspaceLib] Added new ``conditionSet`` element to ``rule`` element in
- designspace document. Bumped ``format`` attribute to ``4.0`` (previously,
- it was formatted as an integer). Removed ``checkDefault``, ``checkAxes``
- methods, and any kind of guessing about the axes when the ``<axes>`` element
- is missing. The default master is expected at the intersection of all default
- values for each axis (#1254, #1255, #1267).
- - [cffLib] Fixed issues when compiling CFF2 or converting from CFF when the
- font has an FDArray (#1211, #1271).
- - [varLib] Avoid attempting to build ``cvar`` table when ``glyf`` table is not
- present, as is the case for CFF2 fonts.
- - [subset] Handle None coverages in MarkGlyphSets; revert commit 02616ab that
- sets empty Coverage tables in MarkGlyphSets to None, to make OTS happy.
- - [ttFont] Allow to build glyph order from ``maxp.numGlyphs`` when ``post`` or
- ``cmap`` are missing.
- - [ttFont] Added ``__len__`` method to ``_TTGlyphSet``.
- - [glyf] Ensure ``GlyphCoordinates`` never overflow signed shorts (#1230).
- - [py23] Added alias for ``itertools.izip`` shadowing the built-in ``zip``.
- - [loggingTools] Memoize ``log`` property of ``LogMixin`` class (fbab12).
- - [ttx] Impoved test coverage (#1261).
- - [Snippets] Addded script to append a suffix to all family names in a font.
- - [varLib.plot] Make it work with matplotlib >= 2.1 (b38e2b).
-
- 3.26.0 (released 2018-05-03)
- ----------------------------
-
- - [designspace] Added a new optional ``layer`` attribute to the source element,
- and a corresponding ``layerName`` attribute to the ``SourceDescriptor``
- object (#1253).
- Added ``conditionset`` element to the ``rule`` element to the spec, but not
- implemented in designspace reader/writer yet (#1254).
- - [varLib.models] Refine modeling one last time (0ecf5c5).
- - [otBase] Fixed sharing of tables referred to by different offset sizes
- (795f2f9).
- - [subset] Don't drop a GDEF that only has VarStore (fc819d6). Set to None
- empty Coverage tables in MarkGlyphSets (02616ab).
- - [varLib]: Added ``--master-finder`` command-line option (#1249).
- - [varLib.mutator] Prune fvar nameIDs from instance's name table (#1245).
- - [otTables] Allow decompiling bad ClassDef tables with invalid format, with
- warning (#1236).
- - [varLib] Make STAT v1.2 and reuse nameIDs from fvar table (#1242).
- - [varLib.plot] Show master locations. Set axis limits to -1, +1.
- - [subset] Handle HVAR direct mapping. Passthrough 'cvar'.
- Added ``--font-number`` command-line option for collections.
- - [t1Lib] Allow a text encoding to be specified when parsing a Type 1 font
- (#1234). Added ``kind`` argument to T1Font constructor (c5c161c).
- - [ttLib] Added context manager API to ``TTFont`` class, so it can be used in
- ``with`` statements to auto-close the file when exiting the context (#1232).
-
- 3.25.0 (released 2018-04-03)
- ----------------------------
-
- - [varLib] Improved support-resolution algorithm. Previously, the on-axis
- masters would always cut the space. They don't anymore. That's more
- consistent, and fixes the main issue Erik showed at TYPO Labs 2017.
- Any varfont built that had an unusual master configuration will change
- when rebuilt (42bef17, a523a697,
- https://github.com/googlei18n/fontmake/issues/264).
- - [varLib.models] Added a ``main()`` entry point, that takes positions and
- prints model results.
- - [varLib.plot] Added new module to plot a designspace's
- VariationModel. Requires ``matplotlib``.
- - [varLib.mutator] Added -o option to specify output file path (2ef60fa).
- - [otTables] Fixed IndexError while pruning of HVAR pre-write (6b6c34a).
- - [varLib.models] Convert delta array to floats if values overflows signed
- short integer (0055f94).
-
- 3.24.2 (released 2018-03-26)
- ----------------------------
-
- - [otBase] Don't fail during ``ValueRecord`` copy if src has more items.
- We drop hinting in the subsetter by simply changing ValueFormat, without
- cleaning up the actual ValueRecords. This was causing assertion error if
- a variable font was subsetted without hinting and then passed directly to
- the mutator for instantiation without first it saving to disk.
-
- 3.24.1 (released 2018-03-06)
- ----------------------------
-
- - [varLib] Don't remap the same ``DeviceTable`` twice in VarStore optimizer
- (#1206).
- - [varLib] Add ``--disable-iup`` option to ``fonttools varLib`` script,
- and a ``optimize=True`` keyword argument to ``varLib.build`` function,
- to optionally disable IUP optimization while building varfonts.
- - [ttCollection] Fixed issue while decompiling ttc with python3 (#1207).
-
- 3.24.0 (released 2018-03-01)
- ----------------------------
-
- - [ttGlyphPen] Decompose composite glyphs if any components' transform is too
- large to fit a ``F2Dot14`` value, or clamp transform values that are
- (almost) equal to +2.0 to make them fit and avoid decomposing (#1200,
- #1204, #1205).
- - [ttx] Added new ``-g`` option to dump glyphs from the ``glyf`` table
- splitted as individual ttx files (#153, #1035, #1132, #1202).
- - Copied ``ufoLib.filenames`` module to ``fontTools.misc.filenames``, used
- for the ttx split-glyphs option (#1202).
- - [feaLib] Added support for ``cvParameters`` blocks in Character Variant
- feautures ``cv01-cv99`` (#860, #1169).
- - [Snippets] Added ``checksum.py`` script to generate/check SHA1 hash of
- ttx files (#1197).
- - [varLib.mutator] Fixed issue while instantiating some variable fonts
- whereby the horizontal advance width computed from ``gvar`` phantom points
- could turn up to be negative (#1198).
- - [varLib/subset] Fixed issue with subsetting GPOS variation data not
- picking up ``ValueRecord`` ``Device`` objects (54fd71f).
- - [feaLib/voltLib] In all AST elements, the ``location`` is no longer a
- required positional argument, but an optional kewyord argument (defaults
- to ``None``). This will make it easier to construct feature AST from
- code (#1201).
-
-
- 3.23.0 (released 2018-02-26)
- ----------------------------
-
- - [designspaceLib] Added an optional ``lib`` element to the designspace as a
- whole, as well as to the instance elements, to store arbitrary data in a
- property list dictionary, similar to the UFO's ``lib``. Added an optional
- ``font`` attribute to the ``SourceDescriptor``, to allow operating on
- in-memory font objects (#1175).
- - [cffLib] Fixed issue with lazy-loading of attributes when attempting to
- set the CFF TopDict.Encoding (#1177, #1187).
- - [ttx] Fixed regression introduced in 3.22.0 that affected the split tables
- ``-s`` option (#1188).
- - [feaLib] Added ``IncludedFeaNotFound`` custom exception subclass, raised
- when an included feature file cannot be found (#1186).
- - [otTables] Changed ``VarIdxMap`` to use glyph names internally instead of
- glyph indexes. The old ttx dumps of HVAR/VVAR tables that contain indexes
- can still be imported (21cbab8, 38a0ffb).
- - [varLib] Implemented VarStore optimizer (#1184).
- - [subset] Implemented pruning of GDEF VarStore, HVAR and MVAR (#1179).
- - [sfnt] Restore backward compatiblity with ``numFonts`` attribute of
- ``SFNTReader`` object (#1181).
- - [merge] Initial support for merging ``LangSysRecords`` (#1180).
- - [ttCollection] don't seek(0) when writing to possibly unseekable strems.
- - [subset] Keep all ``--name-IDs`` from 0 to 6 by default (#1170, #605, #114).
- - [cffLib] Added ``width`` module to calculate optimal CFF default and
- nominal glyph widths.
- - [varLib] Don’t fail if STAT already in the master fonts (#1166).
-
- 3.22.0 (released 2018-02-04)
- ----------------------------
-
- - [subset] Support subsetting ``endchar`` acting as ``seac``-like components
- in ``CFF`` (fixes #1162).
- - [feaLib] Allow to build from pre-parsed ``ast.FeatureFile`` object.
- Added ``tables`` argument to only build some tables instead of all (#1159,
- #1163).
- - [textTools] Replaced ``safeEval`` with ``ast.literal_eval`` (#1139).
- - [feaLib] Added option to the parser to not resolve ``include`` statements
- (#1154).
- - [ttLib] Added new ``ttCollection`` module to read/write TrueType and
- OpenType Collections. Exports a ``TTCollection`` class with a ``fonts``
- attribute containing a list of ``TTFont`` instances, the methods ``save``
- and ``saveXML``, plus some list-like methods. The ``importXML`` method is
- not implemented yet (#17).
- - [unicodeadata] Added ``ot_tag_to_script`` function that converts from
- OpenType script tag to Unicode script code.
- - Added new ``designspaceLib`` subpackage, originally from Erik Van Blokland's
- ``designSpaceDocument``: https://github.com/LettError/designSpaceDocument
- NOTE: this is not yet used internally by varLib, and the API may be subject
- to changes (#911, #1110, LettError/designSpaceDocument#28).
- - Added new FontTools icon images (8ee7c32).
- - [unicodedata] Added ``script_horizontal_direction`` function that returns
- either "LTR" or "RTL" given a unicode script code.
- - [otConverters] Don't write descriptive name string as XML comment if the
- NameID value is 0 (== NULL) (#1151, #1152).
- - [unicodedata] Add ``ot_tags_from_script`` function to get the list of
- OpenType script tags associated with unicode script code (#1150).
- - [feaLib] Don't error when "enumerated" kern pairs conflict with preceding
- single pairs; emit warning and chose the first value (#1147, #1148).
- - [loggingTools] In ``CapturingLogHandler.assertRegex`` method, match the
- fully formatted log message.
- - [sbix] Fixed TypeError when concatenating str and bytes (#1154).
- - [bezierTools] Implemented cusp support and removed ``approximate_fallback``
- arg in ``calcQuadraticArcLength``. Added ``calcCubicArcLength`` (#1142).
-
- 3.21.2 (released 2018-01-08)
- ----------------------------
-
- - [varLib] Fixed merging PairPos Format1/2 with missing subtables (#1125).
-
- 3.21.1 (released 2018-01-03)
- ----------------------------
-
- - [feaLib] Allow mixed single/multiple substitutions (#612)
- - Added missing ``*.afm`` test assets to MAINFEST.in (#1137).
- - Fixed dumping ``SVG`` tables containing color palettes (#1124).
-
- 3.21.0 (released 2017-12-18)
- ----------------------------
-
- - [cmap] when compiling format6 subtable, don't assume gid0 is always called
- '.notdef' (1e42224).
- - [ot] Allow decompiling fonts with bad Coverage format number (1aafae8).
- - Change FontTools licence to MIT (#1127).
- - [post] Prune extra names already in standard Mac set (df1e8c7).
- - [subset] Delete empty SubrsIndex after subsetting (#994, #1118).
- - [varLib] Don't share points in cvar by default, as it currently fails on
- some browsers (#1113).
- - [afmLib] Make poor old afmLib work on python3.
-
- 3.20.1 (released 2017-11-22)
- ----------------------------
-
- - [unicodedata] Fixed issue with ``script`` and ``script_extension`` functions
- returning inconsistent short vs long names. They both return the short four-
- letter script codes now. Added ``script_name`` and ``script_code`` functions
- to look up the long human-readable script name from the script code, and
- viceversa (#1109, #1111).
-
- 3.20.0 (released 2017-11-21)
- ----------------------------
-
- - [unicodedata] Addded new module ``fontTools.unicodedata`` which exports the
- same interface as the built-in ``unicodedata`` module, with the addition of
- a few functions that are missing from the latter, such as ``script``,
- ``script_extension`` and ``block``. Added a ``MetaTools/buildUCD.py`` script
- to download and parse data files from the Unicode Character Database and
- generate python modules containing lists of ranges and property values.
- - [feaLib] Added ``__str__`` method to all ``ast`` elements (delegates to the
- ``asFea`` method).
- - [feaLib] ``Parser`` constructor now accepts a ``glyphNames`` iterable
- instead of ``glyphMap`` dict. The latter still works but with a pending
- deprecation warning (#1104).
- - [bezierTools] Added arc length calculation functions originally from
- ``pens.perimeterPen`` module (#1101).
- - [varLib] Started generating STAT table (8af4309). Right now it just reflects
- the axes, and even that with certain limitations:
- * AxisOrdering is set to the order axes are defined,
- * Name-table entries are not shared with fvar.
- - [py23] Added backports for ``redirect_stdout`` and ``redirect_stderr``
- context managers (#1097).
- - [Graphite] Fixed some round-trip bugs (#1093).
-
- 3.19.0 (released 2017-11-06)
- ----------------------------
-
- - [varLib] Try set of used points instead of all points when testing whether to
- share points between tuples (#1090).
- - [CFF2] Fixed issue with reading/writing PrivateDict BlueValues to TTX file.
- Read the commit message 8b02b5a and issue #1030 for more details.
- NOTE: this change invalidates all the TTX files containing CFF2 tables
- that where dumped with previous verisons of fonttools.
- CFF2 Subr items can have values on the stack after the last operator, thus
- a ``CFF2Subr`` class was added to accommodate this (#1091).
- - [_k_e_r_n] Fixed compilation of AAT kern version=1.0 tables (#1089, #1094)
- - [ttLib] Added getBestCmap() convenience method to TTFont class and cmap table
- class that returns a preferred Unicode cmap subtable given a list of options
- (#1092).
- - [morx] Emit more meaningful subtable flags. Implement InsertionMorphAction
-
- 3.18.0 (released 2017-10-30)
- ----------------------------
-
- - [feaLib] Fixed writing back nested glyph classes (#1086).
- - [TupleVariation] Reactivated shared points logic, bugfixes (#1009).
- - [AAT] Implemented ``morx`` ligature subtables (#1082).
- - [reverseContourPen] Keep duplicate lineTo following a moveTo (#1080,
- https://github.com/googlei18n/cu2qu/issues/51).
- - [varLib.mutator] Suport instantiation of GPOS, GDEF and MVAR (#1079).
- - [sstruct] Fixed issue with ``unicode_literals`` and ``struct`` module in
- old versions of python 2.7 (#993).
-
- 3.17.0 (released 2017-10-16)
- ----------------------------
-
- - [svgPathPen] Added an ``SVGPathPen`` that translates segment pen commands
- into SVG path descriptions. Copied from Tal Leming's ``ufo2svg.svgPathPen``
- https://github.com/typesupply/ufo2svg/blob/d69f992/Lib/ufo2svg/svgPathPen.py
- - [reverseContourPen] Added ``ReverseContourPen``, a filter pen that draws
- contours with the winding direction reversed, while keeping the starting
- point (#1071).
- - [filterPen] Added ``ContourFilterPen`` to manipulate contours as a whole
- rather than segment by segment.
- - [arrayTools] Added ``Vector`` class to apply math operations on an array
- of numbers, and ``pairwise`` function to loop over pairs of items in an
- iterable.
- - [varLib] Added support for building and interpolation of ``cvar`` table
- (f874cf6, a25a401).
-
- 3.16.0 (released 2017-10-03)
- ----------------------------
-
- - [head] Try using ``SOURCE_DATE_EPOCH`` environment variable when setting
- the ``head`` modified timestamp to ensure reproducible builds (#1063).
- See https://reproducible-builds.org/specs/source-date-epoch/
- - [VTT] Decode VTT's ``TSI*`` tables text as UTF-8 (#1060).
- - Added support for Graphite font tables: Feat, Glat, Gloc, Silf and Sill.
- Thanks @mhosken! (#1054).
- - [varLib] Default to using axis "name" attribute if "labelname" element
- is missing (588f524).
- - [merge] Added support for merging Script records. Remove unused features
- and lookups after merge (d802580, 556508b).
- - Added ``fontTools.svgLib`` package. Includes a parser for SVG Paths that
- supports the Pen protocol (#1051). Also, added a snippet to convert SVG
- outlines to UFO GLIF (#1053).
- - [AAT] Added support for ``ankr``, ``bsln``, ``mort``, ``morx``, ``gcid``,
- and ``cidg``.
- - [subset] Implemented subsetting of ``prop``, ``opbd``, ``bsln``, ``lcar``.
-
- 3.15.1 (released 2017-08-18)
- ----------------------------
-
- - [otConverters] Implemented ``__add__`` and ``__radd__`` methods on
- ``otConverters._LazyList`` that decompile a lazy list before adding
- it to another list or ``_LazyList`` instance. Fixes an ``AttributeError``
- in the ``subset`` module when attempting to sum ``_LazyList`` objects
- (6ef48bd2, 1aef1683).
- - [AAT] Support the `opbd` table with optical bounds (a47f6588).
- - [AAT] Support `prop` table with glyph properties (d05617b4).
-
-
- 3.15.0 (released 2017-08-17)
- ----------------------------
-
- - [AAT] Added support for AAT lookups. The ``lcar`` table can be decompiled
- and recompiled; futher work needed to handle ``morx`` table (#1025).
- - [subset] Keep (empty) DefaultLangSys for Script 'DFLT' (6eb807b5).
- - [subset] Support GSUB/GPOS.FeatureVariations (fe01d87b).
- - [varLib] In ``models.supportScalars``, ignore an axis when its peak value
- is 0 (fixes #1020).
- - [varLib] Add default mappings to all axes in avar to fix rendering issue
- in some rasterizers (19c4b377, 04eacf13).
- - [varLib] Flatten multiple tail PairPosFormat2 subtables before merging
- (c55ef525).
- - [ttLib] Added support for recalculating font bounding box in ``CFF`` and
- ``head`` tables, and min/max values in ``hhea`` and ``vhea`` tables (#970).
-
- 3.14.0 (released 2017-07-31)
- ----------------------------
-
- - [varLib.merger] Remove Extensions subtables before merging (f7c20cf8).
- - [varLib] Initialize the avar segment map with required default entries
- (#1014).
- - [varLib] Implemented optimal IUP optmiziation (#1019).
- - [otData] Add ``AxisValueFormat4`` for STAT table v1.2 from OT v1.8.2
- (#1015).
- - [name] Fixed BCP46 language tag for Mac langID=9: 'si' -> 'sl'.
- - [subset] Return value from ``_DehintingT2Decompiler.op_hintmask``
- (c0d672ba).
- - [cffLib] Allow to get TopDict by index as well as by name (dca96c9c).
- - [cffLib] Removed global ``isCFF2`` state; use one set of classes for
- both CFF and CFF2, maintaining backward compatibility existing code (#1007).
- - [cffLib] Deprecated maxstack operator, per OpenType spec update 1.8.1.
- - [cffLib] Added missing default (-100) for UnderlinePosition (#983).
- - [feaLib] Enable setting nameIDs greater than 255 (#1003).
- - [varLib] Recalculate ValueFormat when merging SinglePos (#996).
- - [varLib] Do not emit MVAR if there are no entries in the variation store
- (#987).
- - [ttx] For ``-x`` option, pad with space if table tag length is < 4.
-
- 3.13.1 (released 2017-05-30)
- ----------------------------
-
- - [feaLib.builder] Removed duplicate lookups optimization. The original
- lookup order and semantics of the feature file are preserved (#976).
-
- 3.13.0 (released 2017-05-24)
- ----------------------------
-
- - [varLib.mutator] Implement IUP optimization (#969).
- - [_g_l_y_f.GlyphCoordinates] Changed ``__bool__()`` semantics to match those
- of other iterables (e46f949). Removed ``__abs__()`` (3db5be2).
- - [varLib.interpolate_layout] Added ``mapped`` keyword argument to
- ``interpolate_layout`` to allow disabling avar mapping: if False (default),
- the location is mapped using the map element of the axes in designspace file;
- if True, it is assumed that location is in designspace's internal space and
- no mapping is performed (#950, #975).
- - [varLib.interpolate_layout] Import designspace-loading logic from varLib.
- - [varLib] Fixed bug with recombining PairPosClass2 subtables (81498e5, #914).
- - [cffLib.specializer] When copying iterables, cast to list (462b7f86).
-
- 3.12.1 (released 2017-05-18)
- ----------------------------
-
- - [pens.t2CharStringPen] Fixed AttributeError when calling addComponent in
- T2CharStringPen (#965).
-
- 3.12.0 (released 2017-05-17)
- ----------------------------
-
- - [cffLib.specializer] Added new ``specializer`` module to optimize CFF
- charstrings, used by the T2CharStringPen (#948).
- - [varLib.mutator] Sort glyphs by component depth before calculating composite
- glyphs' bounding boxes to ensure deltas are correctly caclulated (#945).
- - [_g_l_y_f] Fixed loss of precision in GlyphCoordinates by using 'd' (double)
- instead of 'f' (float) as ``array.array`` typecode (#963, #964).
-
- 3.11.0 (released 2017-05-03)
- ----------------------------
-
- - [t2CharStringPen] Initial support for specialized Type2 path operators:
- vmoveto, hmoveto, vlineto, hlineto, vvcurveto, hhcurveto, vhcurveto and
- hvcurveto. This should produce more compact charstrings (#940, #403).
- - [Doc] Added Sphinx sources for the documentation. Thanks @gferreira (#935).
- - [fvar] Expose flags in XML (#932)
- - [name] Add helper function for building multi-lingual names (#921)
- - [varLib] Fixed kern merging when a PairPosFormat2 has ClassDef1 with glyphs
- that are NOT present in the Coverage (1b5e1c4, #939).
- - [varLib] Fixed non-deterministic ClassDef order with PY3 (f056c12, #927).
- - [feLib] Throw an error when the same glyph is defined in multiple mark
- classes within the same lookup (3e3ff00, #453).
-
- 3.10.0 (released 2017-04-14)
- ----------------------------
-
- - [varLib] Added support for building ``avar`` table, using the designspace
- ``<map>`` elements.
- - [varLib] Removed unused ``build(..., axisMap)`` argument. Axis map should
- be specified in designspace file now. We do not accept nonstandard axes
- if ``<axes>`` element is not present.
- - [varLib] Removed "custom" axis from the ``standard_axis_map``. This was
- added before when glyphsLib was always exporting the (unused) custom axis.
- - [varLib] Added partial support for building ``MVAR`` table; does not
- implement ``gasp`` table variations yet.
- - [pens] Added FilterPen base class, for pens that control another pen;
- factored out ``addComponent`` method from BasePen into a separate abstract
- DecomposingPen class; added DecomposingRecordingPen, which records
- components decomposed as regular contours.
- - [TSI1] Fixed computation of the textLength of VTT private tables (#913).
- - [loggingTools] Added ``LogMixin`` class providing a ``log`` property to
- subclasses, which returns a ``logging.Logger`` named after the latter.
- - [loggingTools] Added ``assertRegex`` method to ``CapturingLogHandler``.
- - [py23] Added backport for python 3's ``types.SimpleNamespace`` class.
- - [EBLC] Fixed issue with python 3 ``zip`` iterator.
-
- 3.9.2 (released 2017-04-08)
- ---------------------------
-
- - [pens] Added pen to draw glyphs using WxPython ``GraphicsPath`` class:
- https://wxpython.org/docs/api/wx.GraphicsPath-class.html
- - [varLib.merger] Fixed issue with recombining multiple PairPosFormat2
- subtables (#888)
- - [varLib] Do not encode gvar deltas that are all zeroes, or if all values
- are smaller than tolerance.
- - [ttLib] _TTGlyphSet glyphs now also have ``height`` and ``tsb`` (top
- side bearing) attributes from the ``vmtx`` table, if present.
- - [glyf] In ``GlyphCoordintes`` class, added ``__bool__`` / ``__nonzero__``
- methods, and ``array`` property to get raw array.
- - [ttx] Support reading TTX files with BOM (#896)
- - [CFF2] Fixed the reporting of the number of regions in the font.
-
- 3.9.1 (released 2017-03-20)
- ---------------------------
-
- - [varLib.merger] Fixed issue while recombining multiple PairPosFormat2
- subtables if they were split because of offset overflows (9798c30).
- - [varLib.merger] Only merge multiple PairPosFormat1 subtables if there is
- at least one of the fonts with a non-empty Format1 subtable (0f5a46b).
- - [varLib.merger] Fixed IndexError with empty ClassDef1 in PairPosFormat2
- (aad0d46).
- - [varLib.merger] Avoid reusing Class2Record (mutable) objects (e6125b3).
- - [varLib.merger] Calculate ClassDef1 and ClassDef2's Format when merging
- PairPosFormat2 (23511fd).
- - [macUtils] Added missing ttLib import (b05f203).
-
- 3.9.0 (released 2017-03-13)
- ---------------------------
-
- - [feaLib] Added (partial) support for parsing feature file comments ``# ...``
- appearing in between statements (#879).
- - [feaLib] Cleaned up syntax tree for FeatureNames.
- - [ttLib] Added support for reading/writing ``CFF2`` table (thanks to
- @readroberts at Adobe), and ``TTFA`` (ttfautohint) table.
- - [varLib] Fixed regression introduced with 3.8.0 in the calculation of
- ``NumShorts``, i.e. the number of deltas in ItemVariationData's delta sets
- that use a 16-bit representation (b2825ff).
-
- 3.8.0 (released 2017-03-05)
- ---------------------------
-
- - New pens: MomentsPen, StatisticsPen, RecordingPen, and TeePen.
- - [misc] Added new ``fontTools.misc.symfont`` module, for symbolic font
- statistical analysis; requires ``sympy`` (http://www.sympy.org/en/index.html)
- - [varLib] Added experimental ``fontTools.varLib.interpolatable`` module for
- finding wrong contour order between different masters
- - [varLib] designspace.load() now returns a dictionary, instead of a tuple,
- and supports <axes> element (#864); the 'masters' item was renamed 'sources',
- like the <sources> element in the designspace document
- - [ttLib] Fixed issue with recalculating ``head`` modified timestamp when
- saving CFF fonts
- - [ttLib] In TupleVariation, round deltas before compiling (#861, fixed #592)
- - [feaLib] Ignore duplicate glyphs in classes used as MarkFilteringSet and
- MarkAttachmentType (#863)
- - [merge] Changed the ``gasp`` table merge logic so that only the one from
- the first font is retained, similar to other hinting tables (#862)
- - [Tests] Added tests for the ``varLib`` package, as well as test fonts
- from the "Annotated OpenType Specification" (AOTS) to exercise ``ttLib``'s
- table readers/writers (<https://github.com/adobe-type-tools/aots>)
-
- 3.7.2 (released 2017-02-17)
- ---------------------------
-
- - [subset] Keep advance widths when stripping ".notdef" glyph outline in
- CID-keyed CFF fonts (#845)
- - [feaLib] Zero values now produce the same results as makeotf (#633, #848)
- - [feaLib] More compact encoding for “Contextual positioning with in-line
- single positioning rules” (#514)
-
- 3.7.1 (released 2017-02-15)
- ---------------------------
-
- - [subset] Fixed issue with ``--no-hinting`` option whereby advance widths in
- Type 2 charstrings were also being stripped (#709, #343)
- - [feaLib] include statements now resolve relative paths like makeotf (#838)
- - [feaLib] table ``name`` now handles Unicode codepoints beyond the Basic
- Multilingual Plane, also supports old-style MacOS platform encodings (#842)
- - [feaLib] correctly escape string literals when emitting feature syntax (#780)
-
- 3.7.0 (released 2017-02-11)
- ---------------------------
-
- - [ttx, mtiLib] Preserve ordering of glyph alternates in GSUB type 3 (#833).
- - [feaLib] Glyph names can have dashes, as per new AFDKO syntax v1.20 (#559).
- - [feaLib] feaLib.Parser now needs the font's glyph map for parsing.
- - [varLib] Fix regression where GPOS values were stored as 0.
- - [varLib] Allow merging of class-based kerning when ClassDefs are different
-
- 3.6.3 (released 2017-02-06)
- ---------------------------
-
- - [varLib] Fix building variation of PairPosFormat2 (b5c34ce).
- - Populate defaults even for otTables that have postRead (e45297b).
- - Fix compiling of MultipleSubstFormat1 with zero 'out' glyphs (b887860).
-
- 3.6.2 (released 2017-01-30)
- ---------------------------
-
- - [varLib.merger] Fixed "TypeError: reduce() of empty sequence with no
- initial value" (3717dc6).
-
- 3.6.1 (released 2017-01-28)
- ---------------------------
-
- - [py23] Fixed unhandled exception occurring at interpreter shutdown in
- the "last resort" logging handler (972b3e6).
- - [agl] Ensure all glyph names are of native 'str' type; avoid mixing
- 'str' and 'unicode' in TTFont.glyphOrder (d8c4058).
- - Fixed inconsistent title levels in README.rst that caused PyPI to
- incorrectly render the reStructuredText page.
-
- 3.6.0 (released 2017-01-26)
- ---------------------------
-
- - [varLib] Refactored and improved the variation-font-building process.
- - Assembly code in the fpgm, prep, and glyf tables is now indented in
- XML output for improved readability. The ``instruction`` element is
- written as a simple tag if empty (#819).
- - [ttx] Fixed 'I/O operation on closed file' error when dumping
- multiple TTXs to standard output with the '-o -' option.
- - The unit test modules (``*_test.py``) have been moved outside of the
- fontTools package to the Tests folder, thus they are no longer
- installed (#811).
-
- 3.5.0 (released 2017-01-14)
- ---------------------------
-
- - Font tables read from XML can now be written back to XML with no
- loss.
- - GSUB/GPOS LookupType is written out in XML as an element, not
- comment. (#792)
- - When parsing cmap table, do not store items mapped to glyph id 0.
- (#790)
- - [otlLib] Make ClassDef sorting deterministic. Fixes #766 (7d1ddb2)
- - [mtiLib] Added unit tests (#787)
- - [cvar] Implemented cvar table
- - [gvar] Renamed GlyphVariation to TupleVariation to match OpenType
- terminology.
- - [otTables] Handle gracefully empty VarData.Item array when compiling
- XML. (#797)
- - [varLib] Re-enabled generation of ``HVAR`` table for fonts with
- TrueType outlines; removed ``--build-HVAR`` command-line option.
- - [feaLib] The parser can now be extended to support non-standard
- statements in FEA code by using a customized Abstract Syntax Tree.
- See, for example, ``feaLib.builder_test.test_extensions`` and
- baseClass.feax (#794, fixes #773).
- - [feaLib] Added ``feaLib`` command to the 'fonttools' command-line
- tool; applies a feature file to a font. ``fonttools feaLib -h`` for
- help.
- - [pens] The ``T2CharStringPen`` now takes an optional
- ``roundTolerance`` argument to control the rounding of coordinates
- (#804, fixes #769).
- - [ci] Measure test coverage on all supported python versions and OSes,
- combine coverage data and upload to
- https://codecov.io/gh/fonttools/fonttools (#786)
- - [ci] Configured Travis and Appveyor for running tests on Python 3.6
- (#785, 55c03bc)
- - The manual pages installation directory can be customized through
- ``FONTTOOLS_MANPATH`` environment variable (#799, fixes #84).
- - [Snippets] Added otf2ttf.py, for converting fonts from CFF to
- TrueType using the googlei18n/cu2qu module (#802)
-
- 3.4.0 (released 2016-12-21)
- ---------------------------
-
- - [feaLib] Added support for generating FEA text from abstract syntax
- tree (AST) objects (#776). Thanks @mhosken
- - Added ``agl.toUnicode`` function to convert AGL-compliant glyph names
- to Unicode strings (#774)
- - Implemented MVAR table (b4d5381)
-
- 3.3.1 (released 2016-12-15)
- ---------------------------
-
- - [setup] We no longer use versioneer.py to compute fonttools version
- from git metadata, as this has caused issues for some users (#767).
- Now we bump the version strings manually with a custom ``release``
- command of setup.py script.
-
- 3.3.0 (released 2016-12-06)
- ---------------------------
-
- - [ttLib] Implemented STAT table from OpenType 1.8 (#758)
- - [cffLib] Fixed decompilation of CFF fonts containing non-standard
- key/value pairs in FontDict (issue #740; PR #744)
- - [py23] minor: in ``round3`` function, allow the second argument to be
- ``None`` (#757)
- - The standalone ``sstruct`` and ``xmlWriter`` modules, deprecated
- since vesion 3.2.0, have been removed. They can be imported from the
- ``fontTools.misc`` package.
-
- 3.2.3 (released 2016-12-02)
- ---------------------------
-
- - [py23] optimized performance of round3 function; added backport for
- py35 math.isclose() (9d8dacb)
- - [subset] fixed issue with 'narrow' (UCS-2) Python 2 builds and
- ``--text``/``--text-file`` options containing non-BMP chararcters
- (16d0e5e)
- - [varLib] fixed issuewhen normalizing location values (8fa2ee1, #749)
- - [inspect] Made it compatible with both python2 and python3 (167ee60,
- #748). Thanks @pnemade
-
- 3.2.2 (released 2016-11-24)
- ---------------------------
-
- - [varLib] Do not emit null axes in fvar (1bebcec). Thanks @robmck-ms
- - [varLib] Handle fonts without GPOS (7915a45)
- - [merge] Ignore LangSys if None (a11bc56)
- - [subset] Fix subsetting MathVariants (78d3cbe)
- - [OS/2] Fix "Private Use (plane 15)" range (08a0d55). Thanks @mashabow
-
- 3.2.1 (released 2016-11-03)
- ---------------------------
-
- - [OS/2] fix checking ``fsSelection`` bits matching ``head.macStyle``
- bits
- - [varLib] added ``--build-HVAR`` option to generate ``HVAR`` table for
- fonts with TrueType outlines. For ``CFF2``, it is enabled by default.
-
- 3.2.0 (released 2016-11-02)
- ---------------------------
-
- - [varLib] Improve support for OpenType 1.8 Variable Fonts:
- - Implement GDEF's VariationStore
- - Implement HVAR/VVAR tables
- - Partial support for loading MutatorMath .designspace files with
- varLib.designspace module
- - Add varLib.models with Variation fonts interpolation models
- - Implement GSUB/GPOS FeatureVariations
- - Initial support for interpolating and merging OpenType Layout tables
- (see ``varLib.interpolate_layout`` and ``varLib.merger`` modules)
- - [API change] Change version to be an integer instead of a float in
- XML output for GSUB, GPOS, GDEF, MATH, BASE, JSTF, HVAR, VVAR, feat,
- hhea and vhea tables. Scripts that set the Version for those to 1.0
- or other float values also need fixing. A warning is emitted when
- code or XML needs fix.
- - several bug fixes to the cffLib module, contributed by Adobe's
- @readroberts
- - The XML output for CFF table now has a 'major' and 'minor' elements
- for specifying whether it's version 1.0 or 2.0 (support for CFF2 is
- coming soon)
- - [setup.py] remove undocumented/deprecated ``extra_path`` Distutils
- argument. This means that we no longer create a "FontTools" subfolder
- in site-packages containing the actual fontTools package, as well as
- the standalone xmlWriter and sstruct modules. The latter modules are
- also deprecated, and scheduled for removal in upcoming releases.
- Please change your import statements to point to from fontTools.misc
- import xmlWriter and from fontTools.misc import sstruct.
- - [scripts] Add a 'fonttools' command-line tool that simply runs
- ``fontTools.*`` sub-modules: e.g. ``fonttools ttx``,
- ``fonttools subset``, etc.
- - [hmtx/vmts] Read advance width/heights as unsigned short (uint16);
- automatically round float values to integers.
- - [ttLib/xmlWriter] add 'newlinestr=None' keyword argument to
- ``TTFont.saveXML`` for overriding os-specific line endings (passed on
- to ``XMLWriter`` instances).
- - [versioning] Use versioneer instead of ``setuptools_scm`` to
- dynamically load version info from a git checkout at import time.
- - [feaLib] Support backslash-prefixed glyph names.
-
- 3.1.2 (released 2016-09-27)
- ---------------------------
-
- - restore Makefile as an alternative way to build/check/install
- - README.md: update instructions for installing package from source,
- and for running test suite
- - NEWS: Change log was out of sync with tagged release
-
- 3.1.1 (released 2016-09-27)
- ---------------------------
-
- - Fix ``ttLibVersion`` attribute in TTX files still showing '3.0'
- instead of '3.1'.
- - Use ``setuptools_scm`` to manage package versions.
-
- 3.1.0 (released 2016-09-26)
- ---------------------------
-
- - [feaLib] New library to parse and compile Adobe FDK OpenType Feature
- files.
- - [mtiLib] New library to parse and compile Monotype 'FontDame'
- OpenType Layout Tables files.
- - [voltLib] New library to parse Microsoft VOLT project files.
- - [otlLib] New library to work with OpenType Layout tables.
- - [varLib] New library to work with OpenType Font Variations.
- - [pens] Add ttGlyphPen to draw to TrueType glyphs, and t2CharStringPen
- to draw to Type 2 Charstrings (CFF); add areaPen and perimeterPen.
- - [ttLib.tables] Implement 'meta' and 'trak' tables.
- - [ttx] Add --flavor option for compiling to 'woff' or 'woff2'; add
- ``--with-zopfli`` option to use Zopfli to compress WOFF 1.0 fonts.
- - [subset] Support subsetting 'COLR'/'CPAL' and 'CBDT'/'CBLC' color
- fonts tables, and 'gvar' table for variation fonts.
- - [Snippets] Add ``symfont.py``, for symbolic font statistics analysis;
- interpolatable.py, a preliminary script for detecting interpolation
- errors; ``{merge,dump}_woff_metadata.py``.
- - [classifyTools] Helpers to classify things into classes.
- - [CI] Run tests on Windows, Linux and macOS using Appveyor and Travis
- CI; check unit test coverage with Coverage.py/Coveralls; automatic
- deployment to PyPI on tags.
- - [loggingTools] Use Python built-in logging module to print messages.
- - [py23] Make round() behave like Python 3 built-in round(); define
- round2() and round3().
-
- 3.0 (released 2015-09-01)
- -------------------------
-
- - Add Snippet scripts for cmap subtable format conversion, printing
- GSUB/GPOS features, building a GX font from two masters
- - TTX WOFF2 support and a ``-f`` option to overwrite output file(s)
- - Support GX tables: ``avar``, ``gvar``, ``fvar``, ``meta``
- - Support ``feat`` and gzip-compressed SVG tables
- - Upgrade Mac East Asian encodings to native implementation if
- available
- - Add Roman Croatian and Romanian encodings, codecs for mac-extended
- East Asian encodings
- - Implement optimal GLYF glyph outline packing; disabled by default
-
- 2.5 (released 2014-09-24)
- -------------------------
-
- - Add a Qt pen
- - Add VDMX table converter
- - Load all OpenType sub-structures lazily
- - Add support for cmap format 13.
- - Add pyftmerge tool
- - Update to Unicode 6.3.0d3
- - Add pyftinspect tool
- - Add support for Google CBLC/CBDT color bitmaps, standard EBLC/EBDT
- embedded bitmaps, and ``SVG`` table (thanks to Read Roberts at Adobe)
- - Add support for loading, saving and ttx'ing WOFF file format
- - Add support for Microsoft COLR/CPAL layered color glyphs
- - Support PyPy
- - Support Jython, by replacing numpy with array/lists modules and
- removed it, pure-Python StringIO, not cStringIO
- - Add pyftsubset and Subsetter object, supporting CFF and TTF
- - Add to ttx args for -q for quiet mode, -z to choose a bitmap dump
- format
-
- 2.4 (released 2013-06-22)
- -------------------------
-
- - Option to write to arbitrary files
- - Better dump format for DSIG
- - Better detection of OTF XML
- - Fix issue with Apple's kern table format
- - Fix mangling of TT glyph programs
- - Fix issues related to mona.ttf
- - Fix Windows Installer instructions
- - Fix some modern MacOS issues
- - Fix minor issues and typos
-
- 2.3 (released 2009-11-08)
- -------------------------
-
- - TrueType Collection (TTC) support
- - Python 2.6 support
- - Update Unicode data to 5.2.0
- - Couple of bug fixes
-
- 2.2 (released 2008-05-18)
- -------------------------
-
- - ClearType support
- - cmap format 1 support
- - PFA font support
- - Switched from Numeric to numpy
- - Update Unicode data to 5.1.0
- - Update AGLFN data to 1.6
- - Many bug fixes
-
- 2.1 (released 2008-01-28)
- -------------------------
-
- - Many years worth of fixes and features
-
- 2.0b2 (released 2002-??-??)
- ---------------------------
-
- - Be "forgiving" when interpreting the maxp table version field:
- interpret any value as 1.0 if it's not 0.5. Fixes dumping of these
- GPL fonts: http://www.freebsd.org/cgi/pds.cgi?ports/chinese/wangttf
- - Fixed ttx -l: it turned out this part of the code didn't work with
- Python 2.2.1 and earlier. My bad to do most of my testing with a
- different version than I shipped TTX with :-(
- - Fixed bug in ClassDef format 1 subtable (Andreas Seidel bumped into
- this one).
-
- 2.0b1 (released 2002-09-10)
- ---------------------------
-
- - Fixed embarrassing bug: the master checksum in the head table is now
- calculated correctly even on little-endian platforms (such as Intel).
- - Made the cmap format 4 compiler smarter: the binary data it creates
- is now more or less as compact as possible. TTX now makes more
- compact data than in any shipping font I've tested it with.
- - Dump glyph names as a separate "GlyphOrder" pseudo table as opposed
- to as part of the glyf table (obviously needed for CFF-OTF's).
- - Added proper support for the CFF table.
- - Don't barf on empty tables (questionable, but "there are font out
- there...")
- - When writing TT glyf data, align glyphs on 4-byte boundaries. This
- seems to be the current recommendation by MS. Also: don't barf on
- fonts which are already 4-byte aligned.
- - Windows installer contributed bu Adam Twardoch! Yay!
- - Changed the command line interface again, now by creating one new
- tool replacing the old ones: ttx It dumps and compiles, depending on
- input file types. The options have changed somewhat.
- - The -d option is back (output dir)
- - ttcompile's -i options is now called -m (as in "merge"), to avoid
- clash with dump's -i.
- - The -s option ("split tables") no longer creates a directory, but
- instead outputs a small .ttx file containing references to the
- individual table files. This is not a true link, it's a simple file
- name, and the referenced file should be in the same directory so
- ttcompile can find them.
- - compile no longer accepts a directory as input argument. Instead it
- can parse the new "mini-ttx" format as output by "ttx -s".
- - all arguments are input files
- - Renamed the command line programs and moved them to the Tools
- subdirectory. They are now installed by the setup.py install script.
- - Added OpenType support. BASE, GDEF, GPOS, GSUB and JSTF are (almost)
- fully supported. The XML output is not yet final, as I'm still
- considering to output certain subtables in a more human-friendly
- manner.
- - Fixed 'kern' table to correctly accept subtables it doesn't know
- about, as well as interpreting Apple's definition of the 'kern' table
- headers correctly.
- - Fixed bug where glyphnames were not calculated from 'cmap' if it was
- (one of the) first tables to be decompiled. More specifically: it
- cmap was the first to ask for a glyphID -> glyphName mapping.
- - Switched XML parsers: use expat instead of xmlproc. Should be faster.
- - Removed my UnicodeString object: I now require Python 2.0 or up,
- which has unicode support built in.
- - Removed assert in glyf table: redundant data at the end of the table
- is now ignored instead of raising an error. Should become a warning.
- - Fixed bug in hmtx/vmtx code that only occured if all advances were
- equal.
- - Fixed subtle bug in TT instruction disassembler.
- - Couple of fixes to the 'post' table.
- - Updated OS/2 table to latest spec.
-
- 1.0b1 (released 2001-08-10)
- ---------------------------
-
- - Reorganized the command line interface for ttDump.py and
- ttCompile.py, they now behave more like "normal" command line tool,
- in that they accept multiple input files for batch processing.
- - ttDump.py and ttCompile.py don't silently override files anymore, but
- ask before doing so. Can be overridden by -f.
- - Added -d option to both ttDump.py and ttCompile.py.
- - Installation is now done with distutils. (Needs work for environments
- without compilers.)
- - Updated installation instructions.
- - Added some workarounds so as to handle certain buggy fonts more
- gracefully.
- - Updated Unicode table to Unicode 3.0 (Thanks Antoine!)
- - Included a Python script by Adam Twardoch that adds some useful stuff
- to the Windows registry.
- - Moved the project to SourceForge.
-
- 1.0a6 (released 2000-03-15)
- ---------------------------
-
- - Big reorganization: made ttLib a subpackage of the new fontTools
- package, changed several module names. Called the entire suite
- "FontTools"
- - Added several submodules to fontTools, some new, some older.
- - Added experimental CFF/GPOS/GSUB support to ttLib, read-only (but XML
- dumping of GPOS/GSUB is for now disabled)
- - Fixed hdmx endian bug
- - Added -b option to ttCompile.py, it disables recalculation of
- bounding boxes, as requested by Werner Lemberg.
- - Renamed tt2xml.pt to ttDump.py and xml2tt.py to ttCompile.py
- - Use ".ttx" as file extension instead of ".xml".
- - TTX is now the name of the XML-based *format* for TT fonts, and not
- just an application.
-
- 1.0a5
- -----
-
- Never released
-
- - More tables supported: hdmx, vhea, vmtx
-
- 1.0a3 & 1.0a4
- -------------
-
- Never released
-
- - fixed most portability issues
- - retracted the "Euro_or_currency" change from 1.0a2: it was
- nonsense!
-
- 1.0a2 (released 1999-05-02)
- ---------------------------
-
- - binary release for MacOS
- - genenates full FOND resources: including width table, PS font name
- info and kern table if applicable.
- - added cmap format 4 support. Extra: dumps Unicode char names as XML
- comments!
- - added cmap format 6 support
- - now accepts true type files starting with "true" (instead of just
- 0x00010000 and "OTTO")
- - 'glyf' table support is now complete: I added support for composite
- scale, xy-scale and two-by-two for the 'glyf' table. For now,
- component offset scale behaviour defaults to Apple-style. This only
- affects the (re)calculation of the glyph bounding box.
- - changed "Euro" to "Euro_or_currency" in the Standard Apple Glyph
- order list, since we cannot tell from the 'post' table which is
- meant. I should probably doublecheck with a Unicode encoding if
- available. (This does not affect the output!)
-
- Fixed bugs: - 'hhea' table is now recalculated correctly - fixed wrong
- assumption about sfnt resource names
-
- 1.0a1 (released 1999-04-27)
- ---------------------------
-
- - initial binary release for MacOS
-
-Platform: Any
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Environment :: Console
-Classifier: Environment :: Other Environment
-Classifier: Intended Audience :: Developers
-Classifier: Intended Audience :: End Users/Desktop
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Natural Language :: English
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 3
-Classifier: Topic :: Text Processing :: Fonts
-Classifier: Topic :: Multimedia :: Graphics
-Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
-Provides-Extra: woff
-Provides-Extra: all
-Provides-Extra: symfont
-Provides-Extra: interpolatable
-Provides-Extra: unicode
-Provides-Extra: type1
-Provides-Extra: lxml
-Provides-Extra: ufo
-Provides-Extra: plot
-Provides-Extra: graphite
diff --git a/Lib/fonttools.egg-info/SOURCES.txt b/Lib/fonttools.egg-info/SOURCES.txt
deleted file mode 100644
index 29f3b121..00000000
--- a/Lib/fonttools.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,1820 +0,0 @@
-.appveyor.yml
-.codecov.yml
-.coveragerc
-.travis.yml
-LICENSE
-LICENSE.external
-MANIFEST.in
-Makefile
-NEWS.rst
-README.rst
-dev-requirements.txt
-fonttools
-requirements.txt
-run-tests.sh
-setup.cfg
-setup.py
-tox.ini
-.travis/after_success.sh
-.travis/before_install.sh
-.travis/install.sh
-.travis/run.sh
-Doc/Makefile
-Doc/make.bat
-Doc/man/man1/ttx.1
-Doc/source/afmLib.rst
-Doc/source/agl.rst
-Doc/source/cffLib.rst
-Doc/source/conf.py
-Doc/source/encodings.rst
-Doc/source/feaLib.rst
-Doc/source/index.rst
-Doc/source/merge.rst
-Doc/source/subset.rst
-Doc/source/t1Lib.rst
-Doc/source/ttx.rst
-Doc/source/voltLib.rst
-Doc/source/designspaceLib/index.rst
-Doc/source/designspaceLib/readme.rst
-Doc/source/designspaceLib/scripting.rst
-Doc/source/misc/arrayTools.rst
-Doc/source/misc/bezierTools.rst
-Doc/source/misc/classifyTools.rst
-Doc/source/misc/eexec.rst
-Doc/source/misc/encodingTools.rst
-Doc/source/misc/fixedTools.rst
-Doc/source/misc/index.rst
-Doc/source/misc/loggingTools.rst
-Doc/source/misc/psCharStrings.rst
-Doc/source/misc/sstruct.rst
-Doc/source/misc/testTools.rst
-Doc/source/misc/textTools.rst
-Doc/source/misc/timeTools.rst
-Doc/source/misc/transform.rst
-Doc/source/misc/xmlReader.rst
-Doc/source/misc/xmlWriter.rst
-Doc/source/pens/areaPen.rst
-Doc/source/pens/basePen.rst
-Doc/source/pens/boundsPen.rst
-Doc/source/pens/filterPen.rst
-Doc/source/pens/index.rst
-Doc/source/pens/perimeterPen.rst
-Doc/source/pens/pointInsidePen.rst
-Doc/source/pens/recordingPen.rst
-Doc/source/pens/statisticsPen.rst
-Doc/source/pens/t2CharStringPen.rst
-Doc/source/pens/teePen.rst
-Doc/source/pens/transformPen.rst
-Doc/source/ttLib/index.rst
-Doc/source/ttLib/macUtils.rst
-Doc/source/ttLib/sfnt.rst
-Doc/source/ttLib/tables.rst
-Doc/source/ttLib/woff2.rst
-Doc/source/ufoLib/converters.rst
-Doc/source/ufoLib/filenames.rst
-Doc/source/ufoLib/glifLib.rst
-Doc/source/ufoLib/pointPen.rst
-Doc/source/ufoLib/ufoLib.rst
-Doc/source/varLib/designspace.rst
-Doc/source/varLib/index.rst
-Doc/source/varLib/interpolatable.rst
-Doc/source/varLib/interpolate_layout.rst
-Doc/source/varLib/merger.rst
-Doc/source/varLib/models.rst
-Doc/source/varLib/mutator.rst
-Lib/fontTools/__init__.py
-Lib/fontTools/__main__.py
-Lib/fontTools/afmLib.py
-Lib/fontTools/agl.py
-Lib/fontTools/fontBuilder.py
-Lib/fontTools/merge.py
-Lib/fontTools/ttx.py
-Lib/fontTools/unicode.py
-Lib/fontTools/cffLib/__init__.py
-Lib/fontTools/cffLib/specializer.py
-Lib/fontTools/cffLib/width.py
-Lib/fontTools/designspaceLib/__init__.py
-Lib/fontTools/encodings/MacRoman.py
-Lib/fontTools/encodings/StandardEncoding.py
-Lib/fontTools/encodings/__init__.py
-Lib/fontTools/encodings/codecs.py
-Lib/fontTools/feaLib/__init__.py
-Lib/fontTools/feaLib/__main__.py
-Lib/fontTools/feaLib/ast.py
-Lib/fontTools/feaLib/builder.py
-Lib/fontTools/feaLib/error.py
-Lib/fontTools/feaLib/lexer.py
-Lib/fontTools/feaLib/parser.py
-Lib/fontTools/misc/__init__.py
-Lib/fontTools/misc/arrayTools.py
-Lib/fontTools/misc/bezierTools.py
-Lib/fontTools/misc/classifyTools.py
-Lib/fontTools/misc/cliTools.py
-Lib/fontTools/misc/dictTools.py
-Lib/fontTools/misc/eexec.py
-Lib/fontTools/misc/encodingTools.py
-Lib/fontTools/misc/etree.py
-Lib/fontTools/misc/filenames.py
-Lib/fontTools/misc/fixedTools.py
-Lib/fontTools/misc/intTools.py
-Lib/fontTools/misc/loggingTools.py
-Lib/fontTools/misc/macCreatorType.py
-Lib/fontTools/misc/macRes.py
-Lib/fontTools/misc/plistlib.py
-Lib/fontTools/misc/psCharStrings.py
-Lib/fontTools/misc/psLib.py
-Lib/fontTools/misc/psOperators.py
-Lib/fontTools/misc/py23.py
-Lib/fontTools/misc/sstruct.py
-Lib/fontTools/misc/symfont.py
-Lib/fontTools/misc/testTools.py
-Lib/fontTools/misc/textTools.py
-Lib/fontTools/misc/timeTools.py
-Lib/fontTools/misc/transform.py
-Lib/fontTools/misc/xmlReader.py
-Lib/fontTools/misc/xmlWriter.py
-Lib/fontTools/mtiLib/__init__.py
-Lib/fontTools/mtiLib/__main__.py
-Lib/fontTools/otlLib/__init__.py
-Lib/fontTools/otlLib/builder.py
-Lib/fontTools/otlLib/maxContextCalc.py
-Lib/fontTools/pens/__init__.py
-Lib/fontTools/pens/areaPen.py
-Lib/fontTools/pens/basePen.py
-Lib/fontTools/pens/boundsPen.py
-Lib/fontTools/pens/cocoaPen.py
-Lib/fontTools/pens/filterPen.py
-Lib/fontTools/pens/momentsPen.py
-Lib/fontTools/pens/perimeterPen.py
-Lib/fontTools/pens/pointInsidePen.py
-Lib/fontTools/pens/pointPen.py
-Lib/fontTools/pens/qtPen.py
-Lib/fontTools/pens/recordingPen.py
-Lib/fontTools/pens/reportLabPen.py
-Lib/fontTools/pens/reverseContourPen.py
-Lib/fontTools/pens/statisticsPen.py
-Lib/fontTools/pens/svgPathPen.py
-Lib/fontTools/pens/t2CharStringPen.py
-Lib/fontTools/pens/teePen.py
-Lib/fontTools/pens/transformPen.py
-Lib/fontTools/pens/ttGlyphPen.py
-Lib/fontTools/pens/wxPen.py
-Lib/fontTools/subset/__init__.py
-Lib/fontTools/subset/__main__.py
-Lib/fontTools/subset/cff.py
-Lib/fontTools/svgLib/__init__.py
-Lib/fontTools/svgLib/path/__init__.py
-Lib/fontTools/svgLib/path/arc.py
-Lib/fontTools/svgLib/path/parser.py
-Lib/fontTools/svgLib/path/shapes.py
-Lib/fontTools/t1Lib/__init__.py
-Lib/fontTools/ttLib/__init__.py
-Lib/fontTools/ttLib/macUtils.py
-Lib/fontTools/ttLib/sfnt.py
-Lib/fontTools/ttLib/standardGlyphOrder.py
-Lib/fontTools/ttLib/ttCollection.py
-Lib/fontTools/ttLib/ttFont.py
-Lib/fontTools/ttLib/woff2.py
-Lib/fontTools/ttLib/tables/B_A_S_E_.py
-Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py
-Lib/fontTools/ttLib/tables/C_B_D_T_.py
-Lib/fontTools/ttLib/tables/C_B_L_C_.py
-Lib/fontTools/ttLib/tables/C_F_F_.py
-Lib/fontTools/ttLib/tables/C_F_F__2.py
-Lib/fontTools/ttLib/tables/C_O_L_R_.py
-Lib/fontTools/ttLib/tables/C_P_A_L_.py
-Lib/fontTools/ttLib/tables/D_S_I_G_.py
-Lib/fontTools/ttLib/tables/DefaultTable.py
-Lib/fontTools/ttLib/tables/E_B_D_T_.py
-Lib/fontTools/ttLib/tables/E_B_L_C_.py
-Lib/fontTools/ttLib/tables/F_F_T_M_.py
-Lib/fontTools/ttLib/tables/F__e_a_t.py
-Lib/fontTools/ttLib/tables/G_D_E_F_.py
-Lib/fontTools/ttLib/tables/G_M_A_P_.py
-Lib/fontTools/ttLib/tables/G_P_K_G_.py
-Lib/fontTools/ttLib/tables/G_P_O_S_.py
-Lib/fontTools/ttLib/tables/G_S_U_B_.py
-Lib/fontTools/ttLib/tables/G__l_a_t.py
-Lib/fontTools/ttLib/tables/G__l_o_c.py
-Lib/fontTools/ttLib/tables/H_V_A_R_.py
-Lib/fontTools/ttLib/tables/J_S_T_F_.py
-Lib/fontTools/ttLib/tables/L_T_S_H_.py
-Lib/fontTools/ttLib/tables/M_A_T_H_.py
-Lib/fontTools/ttLib/tables/M_E_T_A_.py
-Lib/fontTools/ttLib/tables/M_V_A_R_.py
-Lib/fontTools/ttLib/tables/O_S_2f_2.py
-Lib/fontTools/ttLib/tables/S_I_N_G_.py
-Lib/fontTools/ttLib/tables/S_T_A_T_.py
-Lib/fontTools/ttLib/tables/S_V_G_.py
-Lib/fontTools/ttLib/tables/S__i_l_f.py
-Lib/fontTools/ttLib/tables/S__i_l_l.py
-Lib/fontTools/ttLib/tables/T_S_I_B_.py
-Lib/fontTools/ttLib/tables/T_S_I_C_.py
-Lib/fontTools/ttLib/tables/T_S_I_D_.py
-Lib/fontTools/ttLib/tables/T_S_I_J_.py
-Lib/fontTools/ttLib/tables/T_S_I_P_.py
-Lib/fontTools/ttLib/tables/T_S_I_S_.py
-Lib/fontTools/ttLib/tables/T_S_I_V_.py
-Lib/fontTools/ttLib/tables/T_S_I__0.py
-Lib/fontTools/ttLib/tables/T_S_I__1.py
-Lib/fontTools/ttLib/tables/T_S_I__2.py
-Lib/fontTools/ttLib/tables/T_S_I__3.py
-Lib/fontTools/ttLib/tables/T_S_I__5.py
-Lib/fontTools/ttLib/tables/T_T_F_A_.py
-Lib/fontTools/ttLib/tables/TupleVariation.py
-Lib/fontTools/ttLib/tables/V_D_M_X_.py
-Lib/fontTools/ttLib/tables/V_O_R_G_.py
-Lib/fontTools/ttLib/tables/V_V_A_R_.py
-Lib/fontTools/ttLib/tables/__init__.py
-Lib/fontTools/ttLib/tables/_a_n_k_r.py
-Lib/fontTools/ttLib/tables/_a_v_a_r.py
-Lib/fontTools/ttLib/tables/_b_s_l_n.py
-Lib/fontTools/ttLib/tables/_c_i_d_g.py
-Lib/fontTools/ttLib/tables/_c_m_a_p.py
-Lib/fontTools/ttLib/tables/_c_v_a_r.py
-Lib/fontTools/ttLib/tables/_c_v_t.py
-Lib/fontTools/ttLib/tables/_f_e_a_t.py
-Lib/fontTools/ttLib/tables/_f_p_g_m.py
-Lib/fontTools/ttLib/tables/_f_v_a_r.py
-Lib/fontTools/ttLib/tables/_g_a_s_p.py
-Lib/fontTools/ttLib/tables/_g_c_i_d.py
-Lib/fontTools/ttLib/tables/_g_l_y_f.py
-Lib/fontTools/ttLib/tables/_g_v_a_r.py
-Lib/fontTools/ttLib/tables/_h_d_m_x.py
-Lib/fontTools/ttLib/tables/_h_e_a_d.py
-Lib/fontTools/ttLib/tables/_h_h_e_a.py
-Lib/fontTools/ttLib/tables/_h_m_t_x.py
-Lib/fontTools/ttLib/tables/_k_e_r_n.py
-Lib/fontTools/ttLib/tables/_l_c_a_r.py
-Lib/fontTools/ttLib/tables/_l_o_c_a.py
-Lib/fontTools/ttLib/tables/_l_t_a_g.py
-Lib/fontTools/ttLib/tables/_m_a_x_p.py
-Lib/fontTools/ttLib/tables/_m_e_t_a.py
-Lib/fontTools/ttLib/tables/_m_o_r_t.py
-Lib/fontTools/ttLib/tables/_m_o_r_x.py
-Lib/fontTools/ttLib/tables/_n_a_m_e.py
-Lib/fontTools/ttLib/tables/_o_p_b_d.py
-Lib/fontTools/ttLib/tables/_p_o_s_t.py
-Lib/fontTools/ttLib/tables/_p_r_e_p.py
-Lib/fontTools/ttLib/tables/_p_r_o_p.py
-Lib/fontTools/ttLib/tables/_s_b_i_x.py
-Lib/fontTools/ttLib/tables/_t_r_a_k.py
-Lib/fontTools/ttLib/tables/_v_h_e_a.py
-Lib/fontTools/ttLib/tables/_v_m_t_x.py
-Lib/fontTools/ttLib/tables/asciiTable.py
-Lib/fontTools/ttLib/tables/grUtils.py
-Lib/fontTools/ttLib/tables/otBase.py
-Lib/fontTools/ttLib/tables/otConverters.py
-Lib/fontTools/ttLib/tables/otData.py
-Lib/fontTools/ttLib/tables/otTables.py
-Lib/fontTools/ttLib/tables/sbixGlyph.py
-Lib/fontTools/ttLib/tables/sbixStrike.py
-Lib/fontTools/ttLib/tables/table_API_readme.txt
-Lib/fontTools/ttLib/tables/ttProgram.py
-Lib/fontTools/ufoLib/__init__.py
-Lib/fontTools/ufoLib/converters.py
-Lib/fontTools/ufoLib/errors.py
-Lib/fontTools/ufoLib/etree.py
-Lib/fontTools/ufoLib/filenames.py
-Lib/fontTools/ufoLib/glifLib.py
-Lib/fontTools/ufoLib/kerning.py
-Lib/fontTools/ufoLib/plistlib.py
-Lib/fontTools/ufoLib/pointPen.py
-Lib/fontTools/ufoLib/utils.py
-Lib/fontTools/ufoLib/validators.py
-Lib/fontTools/unicodedata/Blocks.py
-Lib/fontTools/unicodedata/OTTags.py
-Lib/fontTools/unicodedata/ScriptExtensions.py
-Lib/fontTools/unicodedata/Scripts.py
-Lib/fontTools/unicodedata/__init__.py
-Lib/fontTools/varLib/__init__.py
-Lib/fontTools/varLib/__main__.py
-Lib/fontTools/varLib/builder.py
-Lib/fontTools/varLib/cff.py
-Lib/fontTools/varLib/featureVars.py
-Lib/fontTools/varLib/instancer.py
-Lib/fontTools/varLib/interpolatable.py
-Lib/fontTools/varLib/interpolate_layout.py
-Lib/fontTools/varLib/iup.py
-Lib/fontTools/varLib/merger.py
-Lib/fontTools/varLib/models.py
-Lib/fontTools/varLib/mutator.py
-Lib/fontTools/varLib/mvar.py
-Lib/fontTools/varLib/plot.py
-Lib/fontTools/varLib/varStore.py
-Lib/fontTools/voltLib/__init__.py
-Lib/fontTools/voltLib/ast.py
-Lib/fontTools/voltLib/error.py
-Lib/fontTools/voltLib/lexer.py
-Lib/fontTools/voltLib/parser.py
-Lib/fonttools.egg-info/PKG-INFO
-Lib/fonttools.egg-info/SOURCES.txt
-Lib/fonttools.egg-info/dependency_links.txt
-Lib/fonttools.egg-info/entry_points.txt
-Lib/fonttools.egg-info/requires.txt
-Lib/fonttools.egg-info/top_level.txt
-MetaTools/buildTableList.py
-MetaTools/buildUCD.py
-MetaTools/roundTrip.py
-Snippets/README.md
-Snippets/checksum.py
-Snippets/cmap-format.py
-Snippets/dump_woff_metadata.py
-Snippets/edit_raw_table_data.py
-Snippets/fix-dflt-langsys.py
-Snippets/interpolate.py
-Snippets/layout-features.py
-Snippets/merge_woff_metadata.py
-Snippets/otf2ttf.py
-Snippets/rename-fonts.py
-Snippets/subset-fpgm.py
-Snippets/svg2glif.py
-Tests/agl_test.py
-Tests/conftest.py
-Tests/merge_test.py
-Tests/unicodedata_test.py
-Tests/afmLib/afmLib_test.py
-Tests/afmLib/data/TestAFM.afm
-Tests/cffLib/cffLib_test.py
-Tests/cffLib/specializer_test.py
-Tests/cffLib/data/TestCFF2Widths.ttx
-Tests/cffLib/data/TestFDSelect4.ttx
-Tests/cffLib/data/TestOTF.ttx
-Tests/cffLib/data/TestSparseCFF2VF.ttx
-Tests/designspaceLib/designspace_test.py
-Tests/designspaceLib/data/test.designspace
-Tests/encodings/codecs_test.py
-Tests/feaLib/__init__.py
-Tests/feaLib/ast_test.py
-Tests/feaLib/builder_test.py
-Tests/feaLib/error_test.py
-Tests/feaLib/lexer_test.py
-Tests/feaLib/parser_test.py
-Tests/feaLib/data/AlternateSubtable.fea
-Tests/feaLib/data/AlternateSubtable.ttx
-Tests/feaLib/data/Attach.fea
-Tests/feaLib/data/Attach.ttx
-Tests/feaLib/data/ChainPosSubtable.fea
-Tests/feaLib/data/ChainPosSubtable.ttx
-Tests/feaLib/data/ChainSubstSubtable.fea
-Tests/feaLib/data/ChainSubstSubtable.ttx
-Tests/feaLib/data/GPOS_1.fea
-Tests/feaLib/data/GPOS_1.ttx
-Tests/feaLib/data/GPOS_1_zero.fea
-Tests/feaLib/data/GPOS_1_zero.ttx
-Tests/feaLib/data/GPOS_2.fea
-Tests/feaLib/data/GPOS_2.ttx
-Tests/feaLib/data/GPOS_2b.fea
-Tests/feaLib/data/GPOS_2b.ttx
-Tests/feaLib/data/GPOS_3.fea
-Tests/feaLib/data/GPOS_3.ttx
-Tests/feaLib/data/GPOS_4.fea
-Tests/feaLib/data/GPOS_4.ttx
-Tests/feaLib/data/GPOS_5.fea
-Tests/feaLib/data/GPOS_5.ttx
-Tests/feaLib/data/GPOS_6.fea
-Tests/feaLib/data/GPOS_6.ttx
-Tests/feaLib/data/GPOS_8.fea
-Tests/feaLib/data/GPOS_8.ttx
-Tests/feaLib/data/GSUB_2.fea
-Tests/feaLib/data/GSUB_2.ttx
-Tests/feaLib/data/GSUB_3.fea
-Tests/feaLib/data/GSUB_3.ttx
-Tests/feaLib/data/GSUB_6.fea
-Tests/feaLib/data/GSUB_6.ttx
-Tests/feaLib/data/GSUB_8.fea
-Tests/feaLib/data/GSUB_8.ttx
-Tests/feaLib/data/GlyphClassDef.fea
-Tests/feaLib/data/GlyphClassDef.ttx
-Tests/feaLib/data/LigatureCaretByIndex.fea
-Tests/feaLib/data/LigatureCaretByIndex.ttx
-Tests/feaLib/data/LigatureCaretByPos.fea
-Tests/feaLib/data/LigatureCaretByPos.ttx
-Tests/feaLib/data/LigatureSubtable.fea
-Tests/feaLib/data/LigatureSubtable.ttx
-Tests/feaLib/data/MultipleSubstSubtable.fea
-Tests/feaLib/data/MultipleSubstSubtable.ttx
-Tests/feaLib/data/PairPosSubtable.fea
-Tests/feaLib/data/PairPosSubtable.ttx
-Tests/feaLib/data/SingleSubstSubtable.fea
-Tests/feaLib/data/SingleSubstSubtable.ttx
-Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.fea
-Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx
-Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.fea
-Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.ttx
-Tests/feaLib/data/ZeroValue_PairPos_horizontal.fea
-Tests/feaLib/data/ZeroValue_PairPos_horizontal.ttx
-Tests/feaLib/data/ZeroValue_PairPos_vertical.fea
-Tests/feaLib/data/ZeroValue_PairPos_vertical.ttx
-Tests/feaLib/data/ZeroValue_SinglePos_horizontal.fea
-Tests/feaLib/data/ZeroValue_SinglePos_horizontal.ttx
-Tests/feaLib/data/ZeroValue_SinglePos_vertical.fea
-Tests/feaLib/data/ZeroValue_SinglePos_vertical.ttx
-Tests/feaLib/data/baseClass.fea
-Tests/feaLib/data/baseClass.feax
-Tests/feaLib/data/bug1307.fea
-Tests/feaLib/data/bug1307.ttx
-Tests/feaLib/data/bug1459.fea
-Tests/feaLib/data/bug1459.ttx
-Tests/feaLib/data/bug453.fea
-Tests/feaLib/data/bug453.ttx
-Tests/feaLib/data/bug457.fea
-Tests/feaLib/data/bug457.ttx
-Tests/feaLib/data/bug463.fea
-Tests/feaLib/data/bug463.ttx
-Tests/feaLib/data/bug501.fea
-Tests/feaLib/data/bug501.ttx
-Tests/feaLib/data/bug502.fea
-Tests/feaLib/data/bug502.ttx
-Tests/feaLib/data/bug504.fea
-Tests/feaLib/data/bug504.ttx
-Tests/feaLib/data/bug505.fea
-Tests/feaLib/data/bug505.ttx
-Tests/feaLib/data/bug506.fea
-Tests/feaLib/data/bug506.ttx
-Tests/feaLib/data/bug509.fea
-Tests/feaLib/data/bug509.ttx
-Tests/feaLib/data/bug512.fea
-Tests/feaLib/data/bug512.ttx
-Tests/feaLib/data/bug514.fea
-Tests/feaLib/data/bug514.ttx
-Tests/feaLib/data/bug568.fea
-Tests/feaLib/data/bug568.ttx
-Tests/feaLib/data/bug633.fea
-Tests/feaLib/data/bug633.ttx
-Tests/feaLib/data/enum.fea
-Tests/feaLib/data/enum.ttx
-Tests/feaLib/data/feature_aalt.fea
-Tests/feaLib/data/feature_aalt.ttx
-Tests/feaLib/data/ignore_pos.fea
-Tests/feaLib/data/ignore_pos.ttx
-Tests/feaLib/data/include0.fea
-Tests/feaLib/data/language_required.fea
-Tests/feaLib/data/language_required.ttx
-Tests/feaLib/data/lookup.fea
-Tests/feaLib/data/lookup.ttx
-Tests/feaLib/data/lookupflag.fea
-Tests/feaLib/data/lookupflag.ttx
-Tests/feaLib/data/markClass.fea
-Tests/feaLib/data/markClass.ttx
-Tests/feaLib/data/mini.fea
-Tests/feaLib/data/multiple_feature_blocks.fea
-Tests/feaLib/data/multiple_feature_blocks.ttx
-Tests/feaLib/data/name.fea
-Tests/feaLib/data/name.ttx
-Tests/feaLib/data/omitted_GlyphClassDef.fea
-Tests/feaLib/data/omitted_GlyphClassDef.ttx
-Tests/feaLib/data/size.fea
-Tests/feaLib/data/size.ttx
-Tests/feaLib/data/size2.fea
-Tests/feaLib/data/size2.ttx
-Tests/feaLib/data/spec10.fea
-Tests/feaLib/data/spec10.ttx
-Tests/feaLib/data/spec4h1.fea
-Tests/feaLib/data/spec4h1.ttx
-Tests/feaLib/data/spec4h2.fea
-Tests/feaLib/data/spec4h2.ttx
-Tests/feaLib/data/spec5d1.fea
-Tests/feaLib/data/spec5d1.ttx
-Tests/feaLib/data/spec5d2.fea
-Tests/feaLib/data/spec5d2.ttx
-Tests/feaLib/data/spec5f_ii_1.fea
-Tests/feaLib/data/spec5f_ii_1.ttx
-Tests/feaLib/data/spec5f_ii_2.fea
-Tests/feaLib/data/spec5f_ii_2.ttx
-Tests/feaLib/data/spec5f_ii_3.fea
-Tests/feaLib/data/spec5f_ii_3.ttx
-Tests/feaLib/data/spec5f_ii_4.fea
-Tests/feaLib/data/spec5f_ii_4.ttx
-Tests/feaLib/data/spec5fi1.fea
-Tests/feaLib/data/spec5fi1.ttx
-Tests/feaLib/data/spec5fi2.fea
-Tests/feaLib/data/spec5fi2.ttx
-Tests/feaLib/data/spec5fi3.fea
-Tests/feaLib/data/spec5fi3.ttx
-Tests/feaLib/data/spec5fi4.fea
-Tests/feaLib/data/spec5fi4.ttx
-Tests/feaLib/data/spec5h1.fea
-Tests/feaLib/data/spec5h1.ttx
-Tests/feaLib/data/spec6b_ii.fea
-Tests/feaLib/data/spec6b_ii.ttx
-Tests/feaLib/data/spec6d2.fea
-Tests/feaLib/data/spec6d2.ttx
-Tests/feaLib/data/spec6e.fea
-Tests/feaLib/data/spec6e.ttx
-Tests/feaLib/data/spec6f.fea
-Tests/feaLib/data/spec6f.ttx
-Tests/feaLib/data/spec6h_ii.fea
-Tests/feaLib/data/spec6h_ii.ttx
-Tests/feaLib/data/spec6h_iii_1.fea
-Tests/feaLib/data/spec6h_iii_1.ttx
-Tests/feaLib/data/spec6h_iii_3d.fea
-Tests/feaLib/data/spec6h_iii_3d.ttx
-Tests/feaLib/data/spec8a.fea
-Tests/feaLib/data/spec8a.ttx
-Tests/feaLib/data/spec8b.fea
-Tests/feaLib/data/spec8b.ttx
-Tests/feaLib/data/spec8c.fea
-Tests/feaLib/data/spec8c.ttx
-Tests/feaLib/data/spec8d.fea
-Tests/feaLib/data/spec8d.ttx
-Tests/feaLib/data/spec9a.fea
-Tests/feaLib/data/spec9a.ttx
-Tests/feaLib/data/spec9b.fea
-Tests/feaLib/data/spec9b.ttx
-Tests/feaLib/data/spec9c1.fea
-Tests/feaLib/data/spec9c1.ttx
-Tests/feaLib/data/spec9c2.fea
-Tests/feaLib/data/spec9c2.ttx
-Tests/feaLib/data/spec9c3.fea
-Tests/feaLib/data/spec9c3.ttx
-Tests/feaLib/data/spec9d.fea
-Tests/feaLib/data/spec9d.ttx
-Tests/feaLib/data/spec9e.fea
-Tests/feaLib/data/spec9e.ttx
-Tests/feaLib/data/spec9f.fea
-Tests/feaLib/data/spec9f.ttx
-Tests/feaLib/data/spec9g.fea
-Tests/feaLib/data/spec9g.ttx
-Tests/feaLib/data/include/include1.fea
-Tests/feaLib/data/include/include3.fea
-Tests/feaLib/data/include/include4.fea
-Tests/feaLib/data/include/include5.fea
-Tests/feaLib/data/include/include6.fea
-Tests/feaLib/data/include/includemissingfile.fea
-Tests/feaLib/data/include/includeself.fea
-Tests/feaLib/data/include/subdir/include2.fea
-Tests/fontBuilder/fontBuilder_test.py
-Tests/fontBuilder/data/test.otf.ttx
-Tests/fontBuilder/data/test.ttf.ttx
-Tests/fontBuilder/data/test_uvs.ttf.ttx
-Tests/fontBuilder/data/test_var.otf.ttx
-Tests/fontBuilder/data/test_var.ttf.ttx
-Tests/misc/arrayTools_test.py
-Tests/misc/bezierTools_test.py
-Tests/misc/classifyTools_test.py
-Tests/misc/eexec_test.py
-Tests/misc/encodingTools_test.py
-Tests/misc/etree_test.py
-Tests/misc/filenames_test.py
-Tests/misc/fixedTools_test.py
-Tests/misc/loggingTools_test.py
-Tests/misc/macRes_test.py
-Tests/misc/plistlib_test.py
-Tests/misc/psCharStrings_test.py
-Tests/misc/py23_test.py
-Tests/misc/testTools_test.py
-Tests/misc/textTools_test.py
-Tests/misc/timeTools_test.py
-Tests/misc/transform_test.py
-Tests/misc/xmlReader_test.py
-Tests/misc/xmlWriter_test.py
-Tests/misc/testdata/test.plist
-Tests/mtiLib/mti_test.py
-Tests/mtiLib/data/featurename-backward.ttx.GSUB
-Tests/mtiLib/data/featurename-backward.txt
-Tests/mtiLib/data/featurename-forward.ttx.GSUB
-Tests/mtiLib/data/featurename-forward.txt
-Tests/mtiLib/data/lookupnames-backward.ttx.GSUB
-Tests/mtiLib/data/lookupnames-backward.txt
-Tests/mtiLib/data/lookupnames-forward.ttx.GSUB
-Tests/mtiLib/data/lookupnames-forward.txt
-Tests/mtiLib/data/mixed-toplevels.ttx.GSUB
-Tests/mtiLib/data/mixed-toplevels.txt
-Tests/mtiLib/data/mti/README
-Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS
-Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB
-Tests/mtiLib/data/mti/chained-glyph.txt
-Tests/mtiLib/data/mti/chainedclass.ttx.GSUB
-Tests/mtiLib/data/mti/chainedclass.txt
-Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB
-Tests/mtiLib/data/mti/chainedcoverage.txt
-Tests/mtiLib/data/mti/cmap.ttx
-Tests/mtiLib/data/mti/cmap.ttx.cmap
-Tests/mtiLib/data/mti/cmap.txt
-Tests/mtiLib/data/mti/context-glyph.txt
-Tests/mtiLib/data/mti/contextclass.txt
-Tests/mtiLib/data/mti/contextcoverage.txt
-Tests/mtiLib/data/mti/featuretable.txt
-Tests/mtiLib/data/mti/gdefattach.ttx.GDEF
-Tests/mtiLib/data/mti/gdefattach.txt
-Tests/mtiLib/data/mti/gdefclasses.ttx.GDEF
-Tests/mtiLib/data/mti/gdefclasses.txt
-Tests/mtiLib/data/mti/gdefligcaret.ttx.GDEF
-Tests/mtiLib/data/mti/gdefligcaret.txt
-Tests/mtiLib/data/mti/gdefmarkattach.ttx.GDEF
-Tests/mtiLib/data/mti/gdefmarkattach.txt
-Tests/mtiLib/data/mti/gdefmarkfilter.ttx.GDEF
-Tests/mtiLib/data/mti/gdefmarkfilter.txt
-Tests/mtiLib/data/mti/gposcursive.ttx.GPOS
-Tests/mtiLib/data/mti/gposcursive.txt
-Tests/mtiLib/data/mti/gposkernset.ttx.GPOS
-Tests/mtiLib/data/mti/gposkernset.txt
-Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS
-Tests/mtiLib/data/mti/gposmarktobase.txt
-Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS
-Tests/mtiLib/data/mti/gpospairclass.txt
-Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS
-Tests/mtiLib/data/mti/gpospairglyph.txt
-Tests/mtiLib/data/mti/gpossingle.ttx.GPOS
-Tests/mtiLib/data/mti/gpossingle.txt
-Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB
-Tests/mtiLib/data/mti/gsubalternate.txt
-Tests/mtiLib/data/mti/gsubligature.ttx.GSUB
-Tests/mtiLib/data/mti/gsubligature.txt
-Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB
-Tests/mtiLib/data/mti/gsubmultiple.txt
-Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB
-Tests/mtiLib/data/mti/gsubreversechanined.txt
-Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB
-Tests/mtiLib/data/mti/gsubsingle.txt
-Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS
-Tests/mtiLib/data/mti/mark-to-ligature.txt
-Tests/mtiLib/data/mti/scripttable.ttx.GPOS
-Tests/mtiLib/data/mti/scripttable.ttx.GSUB
-Tests/mtiLib/data/mti/scripttable.txt
-Tests/otlLib/builder_test.py
-Tests/otlLib/maxContextCalc_test.py
-Tests/otlLib/data/gpos_91.ttx
-Tests/otlLib/data/gsub_51.ttx
-Tests/otlLib/data/gsub_52.ttx
-Tests/otlLib/data/gsub_71.ttx
-Tests/pens/areaPen_test.py
-Tests/pens/basePen_test.py
-Tests/pens/boundsPen_test.py
-Tests/pens/perimeterPen_test.py
-Tests/pens/pointInsidePen_test.py
-Tests/pens/pointPen_test.py
-Tests/pens/recordingPen_test.py
-Tests/pens/reverseContourPen_test.py
-Tests/pens/t2CharStringPen_test.py
-Tests/pens/ttGlyphPen_test.py
-Tests/subset/subset_test.py
-Tests/subset/data/Lobster.subset.otf
-Tests/subset/data/Lobster.subset.ttx
-Tests/subset/data/NotdefWidthCID-Regular.ttx
-Tests/subset/data/TestANKR.ttx
-Tests/subset/data/TestBSLN-0.ttx
-Tests/subset/data/TestBSLN-1.ttx
-Tests/subset/data/TestBSLN-2.ttx
-Tests/subset/data/TestBSLN-3.ttx
-Tests/subset/data/TestCID-Regular.ttx
-Tests/subset/data/TestCLR-Regular.ttx
-Tests/subset/data/TestGVAR.ttx
-Tests/subset/data/TestHVVAR.ttx
-Tests/subset/data/TestLCAR-0.ttx
-Tests/subset/data/TestLCAR-1.ttx
-Tests/subset/data/TestMATH-Regular.ttx
-Tests/subset/data/TestOPBD-0.ttx
-Tests/subset/data/TestOPBD-1.ttx
-Tests/subset/data/TestOTF-Regular.ttx
-Tests/subset/data/TestPROP.ttx
-Tests/subset/data/TestTTF-Regular.ttx
-Tests/subset/data/TestTTF-Regular_non_BMP_char.ttx
-Tests/subset/data/expect_HVVAR.ttx
-Tests/subset/data/expect_HVVAR_retain_gids.ttx
-Tests/subset/data/expect_ankr.ttx
-Tests/subset/data/expect_bsln_0.ttx
-Tests/subset/data/expect_bsln_1.ttx
-Tests/subset/data/expect_bsln_2.ttx
-Tests/subset/data/expect_bsln_3.ttx
-Tests/subset/data/expect_desubroutinize_CFF.ttx
-Tests/subset/data/expect_keep_colr.ttx
-Tests/subset/data/expect_keep_gvar.ttx
-Tests/subset/data/expect_keep_gvar_notdef_outline.ttx
-Tests/subset/data/expect_keep_math.ttx
-Tests/subset/data/expect_lcar_0.ttx
-Tests/subset/data/expect_lcar_1.ttx
-Tests/subset/data/expect_math_partial.ttx
-Tests/subset/data/expect_no_hinting_CFF.ttx
-Tests/subset/data/expect_no_hinting_TTF.ttx
-Tests/subset/data/expect_no_hinting_desubroutinize_CFF.ttx
-Tests/subset/data/expect_no_notdef_outline_cid.ttx
-Tests/subset/data/expect_no_notdef_outline_otf.ttx
-Tests/subset/data/expect_no_notdef_outline_ttf.ttx
-Tests/subset/data/expect_notdef_width_cid.ttx
-Tests/subset/data/expect_opbd_0.ttx
-Tests/subset/data/expect_opbd_1.ttx
-Tests/subset/data/expect_prop_0.ttx
-Tests/subset/data/expect_prop_1.ttx
-Tests/subset/data/expect_sbix.ttx
-Tests/subset/data/google_color.ttx
-Tests/subset/data/sbix.ttx
-Tests/subset/data/test_cntrmask_CFF.desub.ttx
-Tests/subset/data/test_cntrmask_CFF.ttx
-Tests/subset/data/test_hinted_subrs_CFF.desub.ttx
-Tests/subset/data/test_hinted_subrs_CFF.ttx
-Tests/subset/data/test_math_partial.ttx
-Tests/svgLib/path/__init__.py
-Tests/svgLib/path/parser_test.py
-Tests/svgLib/path/path_test.py
-Tests/svgLib/path/shapes_test.py
-Tests/t1Lib/t1Lib_test.py
-Tests/t1Lib/data/TestT1-Regular.lwfn
-Tests/t1Lib/data/TestT1-Regular.pfa
-Tests/t1Lib/data/TestT1-Regular.pfb
-Tests/t1Lib/data/TestT1-weird-zeros.pfa
-Tests/ttLib/sfnt_test.py
-Tests/ttLib/woff2_test.py
-Tests/ttLib/data/TestOTF-Regular.otx
-Tests/ttLib/data/TestTTF-Regular.ttx
-Tests/ttLib/data/TestTTFComplex-Regular.ttx
-Tests/ttLib/data/test_woff2_metadata.xml
-Tests/ttLib/tables/C_F_F__2_test.py
-Tests/ttLib/tables/C_F_F_test.py
-Tests/ttLib/tables/C_P_A_L_test.py
-Tests/ttLib/tables/M_V_A_R_test.py
-Tests/ttLib/tables/O_S_2f_2_test.py
-Tests/ttLib/tables/S_T_A_T_test.py
-Tests/ttLib/tables/T_S_I__0_test.py
-Tests/ttLib/tables/T_S_I__1_test.py
-Tests/ttLib/tables/TupleVariation_test.py
-Tests/ttLib/tables/_a_n_k_r_test.py
-Tests/ttLib/tables/_a_v_a_r_test.py
-Tests/ttLib/tables/_b_s_l_n_test.py
-Tests/ttLib/tables/_c_i_d_g_test.py
-Tests/ttLib/tables/_c_m_a_p_test.py
-Tests/ttLib/tables/_c_v_a_r_test.py
-Tests/ttLib/tables/_f_p_g_m_test.py
-Tests/ttLib/tables/_f_v_a_r_test.py
-Tests/ttLib/tables/_g_c_i_d_test.py
-Tests/ttLib/tables/_g_l_y_f_test.py
-Tests/ttLib/tables/_g_v_a_r_test.py
-Tests/ttLib/tables/_h_h_e_a_test.py
-Tests/ttLib/tables/_h_m_t_x_test.py
-Tests/ttLib/tables/_k_e_r_n_test.py
-Tests/ttLib/tables/_l_c_a_r_test.py
-Tests/ttLib/tables/_l_t_a_g_test.py
-Tests/ttLib/tables/_m_e_t_a_test.py
-Tests/ttLib/tables/_m_o_r_t_test.py
-Tests/ttLib/tables/_m_o_r_x_test.py
-Tests/ttLib/tables/_n_a_m_e_test.py
-Tests/ttLib/tables/_o_p_b_d_test.py
-Tests/ttLib/tables/_p_r_o_p_test.py
-Tests/ttLib/tables/_t_r_a_k_test.py
-Tests/ttLib/tables/_v_h_e_a_test.py
-Tests/ttLib/tables/_v_m_t_x_test.py
-Tests/ttLib/tables/otBase_test.py
-Tests/ttLib/tables/otConverters_test.py
-Tests/ttLib/tables/otTables_test.py
-Tests/ttLib/tables/tables_test.py
-Tests/ttLib/tables/ttProgram_test.py
-Tests/ttLib/tables/data/C_F_F_.bin
-Tests/ttLib/tables/data/C_F_F_.ttx
-Tests/ttLib/tables/data/C_F_F__2.bin
-Tests/ttLib/tables/data/C_F_F__2.ttx
-Tests/ttLib/tables/data/_c_m_a_p_format_14.ttx
-Tests/ttLib/tables/data/_c_m_a_p_format_14_bw_compat.ttx
-Tests/ttLib/tables/data/_g_l_y_f_outline_flag_bit6.glyf.bin
-Tests/ttLib/tables/data/_g_l_y_f_outline_flag_bit6.head.bin
-Tests/ttLib/tables/data/_g_l_y_f_outline_flag_bit6.loca.bin
-Tests/ttLib/tables/data/_g_l_y_f_outline_flag_bit6.maxp.bin
-Tests/ttLib/tables/data/_g_l_y_f_outline_flag_bit6.ttx
-Tests/ttLib/tables/data/_h_h_e_a_recalc_OTF.ttx
-Tests/ttLib/tables/data/_h_h_e_a_recalc_TTF.ttx
-Tests/ttLib/tables/data/_h_h_e_a_recalc_empty.ttx
-Tests/ttLib/tables/data/_v_h_e_a_recalc_OTF.ttx
-Tests/ttLib/tables/data/_v_h_e_a_recalc_TTF.ttx
-Tests/ttLib/tables/data/_v_h_e_a_recalc_empty.ttx
-Tests/ttLib/tables/data/ttProgram.ttx
-Tests/ttLib/tables/data/aots/README
-Tests/ttLib/tables/data/aots/base.otf
-Tests/ttLib/tables/data/aots/base.ttx.CFF
-Tests/ttLib/tables/data/aots/base.ttx.OS_2
-Tests/ttLib/tables/data/aots/base.ttx.cmap
-Tests/ttLib/tables/data/aots/base.ttx.head
-Tests/ttLib/tables/data/aots/base.ttx.hhea
-Tests/ttLib/tables/data/aots/base.ttx.hmtx
-Tests/ttLib/tables/data/aots/base.ttx.maxp
-Tests/ttLib/tables/data/aots/base.ttx.name
-Tests/ttLib/tables/data/aots/base.ttx.post
-Tests/ttLib/tables/data/aots/classdef1_font1.otf
-Tests/ttLib/tables/data/aots/classdef1_font1.ttx.GSUB
-Tests/ttLib/tables/data/aots/classdef1_font2.otf
-Tests/ttLib/tables/data/aots/classdef1_font2.ttx.GSUB
-Tests/ttLib/tables/data/aots/classdef1_font3.otf
-Tests/ttLib/tables/data/aots/classdef1_font3.ttx.GSUB
-Tests/ttLib/tables/data/aots/classdef1_font4.otf
-Tests/ttLib/tables/data/aots/classdef1_font4.ttx.GSUB
-Tests/ttLib/tables/data/aots/classdef2_font1.otf
-Tests/ttLib/tables/data/aots/classdef2_font1.ttx.GSUB
-Tests/ttLib/tables/data/aots/classdef2_font2.otf
-Tests/ttLib/tables/data/aots/classdef2_font2.ttx.GSUB
-Tests/ttLib/tables/data/aots/classdef2_font3.otf
-Tests/ttLib/tables/data/aots/classdef2_font3.ttx.GSUB
-Tests/ttLib/tables/data/aots/classdef2_font4.otf
-Tests/ttLib/tables/data/aots/classdef2_font4.ttx.GSUB
-Tests/ttLib/tables/data/aots/cmap0_font1.otf
-Tests/ttLib/tables/data/aots/cmap0_font1.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap10_font1.otf
-Tests/ttLib/tables/data/aots/cmap10_font1.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap10_font2.otf
-Tests/ttLib/tables/data/aots/cmap10_font2.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap12_font1.otf
-Tests/ttLib/tables/data/aots/cmap12_font1.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap14_font1.otf
-Tests/ttLib/tables/data/aots/cmap14_font1.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap2_font1.otf
-Tests/ttLib/tables/data/aots/cmap2_font1.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap4_font1.otf
-Tests/ttLib/tables/data/aots/cmap4_font1.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap4_font2.otf
-Tests/ttLib/tables/data/aots/cmap4_font2.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap4_font3.otf
-Tests/ttLib/tables/data/aots/cmap4_font3.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap4_font4.otf
-Tests/ttLib/tables/data/aots/cmap4_font4.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap6_font1.otf
-Tests/ttLib/tables/data/aots/cmap6_font1.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap6_font2.otf
-Tests/ttLib/tables/data/aots/cmap6_font2.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap8_font1.otf
-Tests/ttLib/tables/data/aots/cmap8_font1.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap_composition_font1.otf
-Tests/ttLib/tables/data/aots/cmap_composition_font1.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap_subtableselection_font1.otf
-Tests/ttLib/tables/data/aots/cmap_subtableselection_font1.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap_subtableselection_font2.otf
-Tests/ttLib/tables/data/aots/cmap_subtableselection_font2.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap_subtableselection_font3.otf
-Tests/ttLib/tables/data/aots/cmap_subtableselection_font3.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap_subtableselection_font4.otf
-Tests/ttLib/tables/data/aots/cmap_subtableselection_font4.ttx.cmap
-Tests/ttLib/tables/data/aots/cmap_subtableselection_font5.otf
-Tests/ttLib/tables/data/aots/cmap_subtableselection_font5.ttx.cmap
-Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos1_1_simple_f1.otf
-Tests/ttLib/tables/data/aots/gpos1_1_simple_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos1_1_simple_f2.otf
-Tests/ttLib/tables/data/aots/gpos1_1_simple_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos1_1_simple_f3.otf
-Tests/ttLib/tables/data/aots/gpos1_1_simple_f3.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos1_1_simple_f4.otf
-Tests/ttLib/tables/data/aots/gpos1_1_simple_f4.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos1_2_font1.otf
-Tests/ttLib/tables/data/aots/gpos1_2_font1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos1_2_font2.otf
-Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_1_font6.otf
-Tests/ttLib/tables/data/aots/gpos2_1_font6.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_1_font7.otf
-Tests/ttLib/tables/data/aots/gpos2_1_font7.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.otf
-Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f2.otf
-Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_1_simple_f1.otf
-Tests/ttLib/tables/data/aots/gpos2_1_simple_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_2_font1.otf
-Tests/ttLib/tables/data/aots/gpos2_2_font1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_2_font2.otf
-Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_2_font3.otf
-Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_2_font4.otf
-Tests/ttLib/tables/data/aots/gpos2_2_font4.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos2_2_font5.otf
-Tests/ttLib/tables/data/aots/gpos2_2_font5.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos3_font1.otf
-Tests/ttLib/tables/data/aots/gpos3_font1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos3_font2.otf
-Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos3_font3.otf
-Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.otf
-Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.otf
-Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos4_simple_1.otf
-Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos5_font1.otf
-Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gpos6_font1.otf
-Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos7_1_font1.otf
-Tests/ttLib/tables/data/aots/gpos7_1_font1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos9_font1.otf
-Tests/ttLib/tables/data/aots/gpos9_font1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos9_font2.otf
-Tests/ttLib/tables/data/aots/gpos9_font2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.otf
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.otf
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.otf
-Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.otf
-Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.otf
-Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.otf
-Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GPOS
-Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub1_1_modulo_f1.otf
-Tests/ttLib/tables/data/aots/gsub1_1_modulo_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub1_1_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub1_1_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub1_2_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub1_2_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub2_1_multiple_sequences_f1.otf
-Tests/ttLib/tables/data/aots/gsub2_1_multiple_sequences_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub2_1_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub2_1_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub3_1_multiple_f1.otf
-Tests/ttLib/tables/data/aots/gsub3_1_multiple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub3_1_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub3_1_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f1.otf
-Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f2.otf
-Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligsets_f1.otf
-Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligsets_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub4_1_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub4_1_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub7_font1.otf
-Tests/ttLib/tables/data/aots/gsub7_font1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub7_font2.otf
-Tests/ttLib/tables/data/aots/gsub7_font2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.otf
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.otf
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.otf
-Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.otf
-Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.otf
-Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.otf
-Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.otf
-Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.otf
-Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.otf
-Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.otf
-Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GSUB
-Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.otf
-Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GDEF
-Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GSUB
-Tests/ttLib/tables/data/graphite/graphite_tests.ttf
-Tests/ttLib/tables/data/graphite/graphite_tests.ttx.Feat
-Tests/ttLib/tables/data/graphite/graphite_tests.ttx.Glat
-Tests/ttLib/tables/data/graphite/graphite_tests.ttx.Glat.setup
-Tests/ttLib/tables/data/graphite/graphite_tests.ttx.Silf
-Tests/ttLib/tables/data/graphite/graphite_tests.ttx.Silf.setup
-Tests/ttLib/tables/data/graphite/graphite_tests.ttx.Sill
-Tests/ttx/ttx_test.py
-Tests/ttx/data/TestBOM.ttx
-Tests/ttx/data/TestDFONT.dfont
-Tests/ttx/data/TestNoSFNT.ttx
-Tests/ttx/data/TestNoXML.ttx
-Tests/ttx/data/TestOTF.otf
-Tests/ttx/data/TestOTF.ttx
-Tests/ttx/data/TestTTC.ttc
-Tests/ttx/data/TestTTF.ttf
-Tests/ttx/data/TestTTF.ttx
-Tests/ttx/data/TestWOFF.woff
-Tests/ttx/data/TestWOFF2.woff2
-Tests/ufoLib/GLIF1_test.py
-Tests/ufoLib/GLIF2_test.py
-Tests/ufoLib/UFO1_test.py
-Tests/ufoLib/UFO2_test.py
-Tests/ufoLib/UFO3_test.py
-Tests/ufoLib/UFOConversion_test.py
-Tests/ufoLib/UFOZ_test.py
-Tests/ufoLib/__init__.py
-Tests/ufoLib/filenames_test.py
-Tests/ufoLib/glifLib_test.py
-Tests/ufoLib/testSupport.py
-Tests/ufoLib/testdata/DemoFont.ufo/fontinfo.plist
-Tests/ufoLib/testdata/DemoFont.ufo/lib.plist
-Tests/ufoLib/testdata/DemoFont.ufo/metainfo.plist
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/A_.glif
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/B_.glif
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/F_.glif
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/F__A__B_.glif
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/G_.glif
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/O_.glif
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/R_.glif
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/a.glif
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/contents.plist
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/testglyph1.glif
-Tests/ufoLib/testdata/DemoFont.ufo/glyphs/testglyph1.reversed.glif
-Tests/ufoLib/testdata/TestFont1 (UFO1).ufo/fontinfo.plist
-Tests/ufoLib/testdata/TestFont1 (UFO1).ufo/groups.plist
-Tests/ufoLib/testdata/TestFont1 (UFO1).ufo/kerning.plist
-Tests/ufoLib/testdata/TestFont1 (UFO1).ufo/lib.plist
-Tests/ufoLib/testdata/TestFont1 (UFO1).ufo/metainfo.plist
-Tests/ufoLib/testdata/TestFont1 (UFO1).ufo/glyphs/A_.glif
-Tests/ufoLib/testdata/TestFont1 (UFO1).ufo/glyphs/B_.glif
-Tests/ufoLib/testdata/TestFont1 (UFO1).ufo/glyphs/contents.plist
-Tests/ufoLib/testdata/TestFont1 (UFO2).ufo/features.fea
-Tests/ufoLib/testdata/TestFont1 (UFO2).ufo/fontinfo.plist
-Tests/ufoLib/testdata/TestFont1 (UFO2).ufo/groups.plist
-Tests/ufoLib/testdata/TestFont1 (UFO2).ufo/kerning.plist
-Tests/ufoLib/testdata/TestFont1 (UFO2).ufo/lib.plist
-Tests/ufoLib/testdata/TestFont1 (UFO2).ufo/metainfo.plist
-Tests/ufoLib/testdata/TestFont1 (UFO2).ufo/glyphs/A_.glif
-Tests/ufoLib/testdata/TestFont1 (UFO2).ufo/glyphs/B_.glif
-Tests/ufoLib/testdata/TestFont1 (UFO2).ufo/glyphs/contents.plist
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/fontinfo.plist
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/kerning.plist
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/layercontents.plist
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/lib.plist
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/metainfo.plist
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/data/com.github.fonttools.ttx/CUST.ttx
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/_notdef.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/a.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/b.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/c.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/contents.plist
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/d.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/e.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/f.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/g.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/h.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/i.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/j.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/k.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/l.glif
-Tests/ufoLib/testdata/TestFont1 (UFO3).ufo/glyphs/space.glif
-Tests/ufoLib/testdata/UFO3-Read Data.ufo/metainfo.plist
-Tests/ufoLib/testdata/UFO3-Read Data.ufo/data/org.unifiedfontobject.file.txt
-Tests/ufoLib/testdata/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/foo.txt
-Tests/ufoLib/testdata/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/bar/lol.txt
-Tests/varLib/__init__.py
-Tests/varLib/builder_test.py
-Tests/varLib/featureVars_test.py
-Tests/varLib/instancer_test.py
-Tests/varLib/interpolatable_test.py
-Tests/varLib/interpolate_layout_test.py
-Tests/varLib/models_test.py
-Tests/varLib/mutator_test.py
-Tests/varLib/varLib_test.py
-Tests/varLib/data/Build.designspace
-Tests/varLib/data/BuildAvarEmptyAxis.designspace
-Tests/varLib/data/BuildAvarIdentityMaps.designspace
-Tests/varLib/data/BuildAvarSingleAxis.designspace
-Tests/varLib/data/BuildGvarCompositeExplicitDelta.designspace
-Tests/varLib/data/FeatureVars.designspace
-Tests/varLib/data/InterpolateLayout.designspace
-Tests/varLib/data/InterpolateLayout2.designspace
-Tests/varLib/data/InterpolateLayout3.designspace
-Tests/varLib/data/KerningMerging.designspace
-Tests/varLib/data/PartialInstancerTest-VF.ttx
-Tests/varLib/data/PartialInstancerTest2-VF.ttx
-Tests/varLib/data/SparseMasters.designspace
-Tests/varLib/data/TestCFF2.designspace
-Tests/varLib/data/TestNonMarkingCFF2.designspace
-Tests/varLib/data/TestSparseCFF2VF.designspace
-Tests/varLib/data/TestVVAR.designspace
-Tests/varLib/data/test_vpal.designspace
-Tests/varLib/data/master_cff2/TestCFF2_Black.ttx
-Tests/varLib/data/master_cff2/TestCFF2_ExtraLight.ttx
-Tests/varLib/data/master_cff2/TestCFF2_Regular.ttx
-Tests/varLib/data/master_kerning_merging/0.ttx
-Tests/varLib/data/master_kerning_merging/1.ttx
-Tests/varLib/data/master_kerning_merging/2.ttx
-Tests/varLib/data/master_non_marking_cff2/TestNonMarkingCFF2_ExtraLight.ttx
-Tests/varLib/data/master_non_marking_cff2/TestNonMarkingCFF2_Regular.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w0.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w1000.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w439.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w440.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w599.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w600.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w669.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w670.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w799.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w800.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w889.00.ttx
-Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w890.00.ttx
-Tests/varLib/data/master_ttx_getvar_ttf/Mutator_Getvar.ttx
-Tests/varLib/data/master_ttx_interpolatable_otf/TestFamily2-Master0.ttx
-Tests/varLib/data/master_ttx_interpolatable_otf/TestFamily2-Master1.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Bold.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Medium.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Regular.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master0.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master1.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master2.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master3.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master4.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily2-Master0.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily2-Master1.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Bold.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Condensed.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedBold.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedLight.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedSemiBold.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Light.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Regular.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-SemiBold.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Italic15.ttx
-Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Regular.ttx
-Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx
-Tests/varLib/data/master_ttx_varfont_ttf/Mutator_IUP.ttx
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/features.fea
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/glyphs/A_.glif
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/glyphs/_notdef.glif
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/glyphs/a.glif
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/glyphs/dollar.glif
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/glyphs/dollar.nostroke.glif
-Tests/varLib/data/master_ufo/TestFamily-Master0.ufo/glyphs/space.glif
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/features.fea
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/glyphs/A_.glif
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/glyphs/_notdef.glif
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/glyphs/a.glif
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/glyphs/dollar.glif
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/glyphs/dollar.nostroke.glif
-Tests/varLib/data/master_ufo/TestFamily-Master1.ufo/glyphs/space.glif
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/features.fea
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/glyphs/A_.glif
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/glyphs/_notdef.glif
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/glyphs/a.glif
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/glyphs/dollar.glif
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/glyphs/dollar.nostroke.glif
-Tests/varLib/data/master_ufo/TestFamily-Master2.ufo/glyphs/space.glif
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/features.fea
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/glyphs/A_.glif
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/glyphs/_notdef.glif
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/glyphs/a.glif
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/glyphs/dollar.glif
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/glyphs/dollar.nostroke.glif
-Tests/varLib/data/master_ufo/TestFamily-Master3.ufo/glyphs/space.glif
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/features.fea
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/glyphs/A_.glif
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/glyphs/_notdef.glif
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/glyphs/a.glif
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/glyphs/dollar.glif
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/glyphs/dollar.nostroke.glif
-Tests/varLib/data/master_ufo/TestFamily-Master4.ufo/glyphs/space.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/features.fea
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/A_.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/A_.sc.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/_notdef.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/a.alt.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/a.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/ampersand.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/atilde.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/circledotted.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/d.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/dieresisbelowcmb.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/dieresiscmb.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/f.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/f_t.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/space.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/t.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/tildebelowcmb.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master0.ufo/glyphs/tildecmb.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/features.fea
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/A_.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/A_.sc.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/_notdef.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/a.alt.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/a.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/ampersand.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/atilde.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/circledotted.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/d.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/dieresisbelowcmb.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/dieresiscmb.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/f.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/f_t.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/space.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/t.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/tildebelowcmb.glif
-Tests/varLib/data/master_ufo/TestFamily2-Master1.ufo/glyphs/tildecmb.glif
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/groups.plist
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/kerning.plist
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/layercontents.plist
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/glyphs/F_.glif
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/glyphs/T_.glif
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/glyphs/l.glif
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/glyphs/layerinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/glyphs/o.glif
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/glyphs/s.glif
-Tests/varLib/data/master_ufo/TestFamily3-Bold.ufo/glyphs/t.glif
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/groups.plist
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/kerning.plist
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/layercontents.plist
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/glyphs/F_.glif
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/glyphs/T_.glif
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/glyphs/l.glif
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/glyphs/layerinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/glyphs/o.glif
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/glyphs/s.glif
-Tests/varLib/data/master_ufo/TestFamily3-Condensed.ufo/glyphs/t.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/groups.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/kerning.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/layercontents.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/glyphs/F_.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/glyphs/T_.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/glyphs/l.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/glyphs/layerinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/glyphs/o.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/glyphs/s.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedBold.ufo/glyphs/t.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/groups.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/kerning.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/layercontents.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/glyphs/F_.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/glyphs/T_.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/glyphs/l.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/glyphs/layerinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/glyphs/o.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/glyphs/s.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedLight.ufo/glyphs/t.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/groups.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/kerning.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/layercontents.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/glyphs/F_.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/glyphs/T_.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/glyphs/l.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/glyphs/layerinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/glyphs/o.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/glyphs/s.glif
-Tests/varLib/data/master_ufo/TestFamily3-CondensedSemiBold.ufo/glyphs/t.glif
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/groups.plist
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/kerning.plist
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/layercontents.plist
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/glyphs/F_.glif
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/glyphs/T_.glif
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/glyphs/l.glif
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/glyphs/layerinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/glyphs/o.glif
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/glyphs/s.glif
-Tests/varLib/data/master_ufo/TestFamily3-Light.ufo/glyphs/t.glif
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/groups.plist
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/kerning.plist
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/layercontents.plist
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/glyphs/F_.glif
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/glyphs/T_.glif
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/glyphs/l.glif
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/glyphs/layerinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/glyphs/o.glif
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/glyphs/s.glif
-Tests/varLib/data/master_ufo/TestFamily3-Regular.ufo/glyphs/t.glif
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/groups.plist
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/kerning.plist
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/layercontents.plist
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/glyphs/F_.glif
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/glyphs/T_.glif
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/glyphs/l.glif
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/glyphs/layerinfo.plist
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/glyphs/o.glif
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/glyphs/s.glif
-Tests/varLib/data/master_ufo/TestFamily3-SemiBold.ufo/glyphs/t.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/features.fea
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/groups.plist
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/layercontents.plist
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs/N_.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs/O_.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs/O_dieresis.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs/dieresiscomb.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs/o.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs/odieresis.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs.public.background/N_.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs.public.background/O_.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs.public.background/contents.plist
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs.public.background/dieresiscomb.glif
-Tests/varLib/data/master_ufo/TestFamily4-Italic15.ufo/glyphs.public.background/o.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/features.fea
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/fontinfo.plist
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/groups.plist
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/kerning.plist
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/layercontents.plist
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/lib.plist
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/metainfo.plist
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs/N_.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs/O_.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs/O_dieresis.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs/contents.plist
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs/dieresiscomb.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs/n.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs/o.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs/odieresis.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs.public.background/N_.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs.public.background/O_.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs.public.background/contents.plist
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs.public.background/dieresiscomb.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs.public.background/n.glif
-Tests/varLib/data/master_ufo/TestFamily4-Regular.ufo/glyphs.public.background/o.glif
-Tests/varLib/data/master_vpal_test/master_vpal_test_0.ttx
-Tests/varLib/data/master_vpal_test/master_vpal_test_1.ttx
-Tests/varLib/data/master_vvar_cff2/TestVVAR.0.ttx
-Tests/varLib/data/master_vvar_cff2/TestVVAR.1.ttx
-Tests/varLib/data/test_results/Build.ttx
-Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx
-Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx
-Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx
-Tests/varLib/data/test_results/BuildGvarCompositeExplicitDelta.ttx
-Tests/varLib/data/test_results/BuildMain.ttx
-Tests/varLib/data/test_results/BuildTestCFF2.ttx
-Tests/varLib/data/test_results/FeatureVars.ttx
-Tests/varLib/data/test_results/InterpolateLayout.ttx
-Tests/varLib/data/test_results/InterpolateLayout2.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff2.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_same.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff2.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_same.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff2.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_same.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_diff.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_same.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_diff.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_same.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_diff.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_same.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_diff.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_same.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_diff.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_same.ttx
-Tests/varLib/data/test_results/InterpolateLayoutGPOS_size_feat_same.ttx
-Tests/varLib/data/test_results/InterpolateLayoutMain.ttx
-Tests/varLib/data/test_results/InterpolateTestCFF2VF.ttx
-Tests/varLib/data/test_results/Mutator.ttx
-Tests/varLib/data/test_results/Mutator_Getvar-instance.ttx
-Tests/varLib/data/test_results/Mutator_IUP-instance.ttx
-Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx
-Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-100,62.5.ttx
-Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx
-Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-400,62.5.ttx
-Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx
-Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-900,62.5.ttx
-Tests/varLib/data/test_results/SparseMasters.ttx
-Tests/varLib/data/test_results/TestNonMarkingCFF2.ttx
-Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
-Tests/varLib/data/test_results/TestVVAR.ttx
-Tests/varLib/data/test_results/test_vpal.ttx
-Tests/voltLib/lexer_test.py
-Tests/voltLib/parser_test.py \ No newline at end of file
diff --git a/Lib/fonttools.egg-info/dependency_links.txt b/Lib/fonttools.egg-info/dependency_links.txt
deleted file mode 100644
index 8b137891..00000000
--- a/Lib/fonttools.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/Lib/fonttools.egg-info/entry_points.txt b/Lib/fonttools.egg-info/entry_points.txt
deleted file mode 100644
index 2c235cff..00000000
--- a/Lib/fonttools.egg-info/entry_points.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-[console_scripts]
-fonttools = fontTools.__main__:main
-pyftmerge = fontTools.merge:main
-pyftsubset = fontTools.subset:main
-ttx = fontTools.ttx:main
-
diff --git a/Lib/fonttools.egg-info/requires.txt b/Lib/fonttools.egg-info/requires.txt
deleted file mode 100644
index 46d1d131..00000000
--- a/Lib/fonttools.egg-info/requires.txt
+++ /dev/null
@@ -1,76 +0,0 @@
-
-[all]
-fs<3,>=2.2.0
-lxml<5,>=4.0
-zopfli>=0.1.4
-lz4>=1.7.4.2
-matplotlib
-sympy
-
-[all:platform_python_implementation != "PyPy"]
-brotli>=1.0.1
-scipy
-
-[all:platform_python_implementation == "PyPy"]
-brotlipy>=0.7.0
-munkres
-
-[all:python_version < "3.4"]
-enum34>=1.1.6
-singledispatch>=3.4.0.3
-typing>=3.6.4
-
-[all:python_version < "3.8" and platform_python_implementation != "PyPy"]
-unicodedata2>=12.0.0
-
-[all:sys_platform == "darwin"]
-xattr
-
-[graphite]
-lz4>=1.7.4.2
-
-[interpolatable]
-
-[interpolatable:platform_python_implementation != "PyPy"]
-scipy
-
-[interpolatable:platform_python_implementation == "PyPy"]
-munkres
-
-[lxml]
-lxml<5,>=4.0
-
-[lxml:python_version < "3.4"]
-singledispatch>=3.4.0.3
-typing>=3.6.4
-
-[plot]
-matplotlib
-
-[symfont]
-sympy
-
-[type1]
-
-[type1:sys_platform == "darwin"]
-xattr
-
-[ufo]
-fs<3,>=2.2.0
-
-[ufo:python_version < "3.4"]
-enum34>=1.1.6
-
-[unicode]
-
-[unicode:python_version < "3.8" and platform_python_implementation != "PyPy"]
-unicodedata2>=12.0.0
-
-[woff]
-zopfli>=0.1.4
-
-[woff:platform_python_implementation != "PyPy"]
-brotli>=1.0.1
-
-[woff:platform_python_implementation == "PyPy"]
-brotlipy>=0.7.0
diff --git a/Lib/fonttools.egg-info/top_level.txt b/Lib/fonttools.egg-info/top_level.txt
deleted file mode 100644
index 9af65ba3..00000000
--- a/Lib/fonttools.egg-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-fontTools
diff --git a/MANIFEST.in b/MANIFEST.in
index 15291123..8e2bcd1d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -11,7 +11,9 @@ include Lib/fontTools/ttLib/tables/table_API_readme.txt
include *requirements.txt
include tox.ini
-include run-tests.sh
+include mypy.ini
+
+recursive-include Lib/fontTools py.typed
include .appveyor.yml
include .codecov.yml
@@ -19,8 +21,13 @@ include .coveragerc
include .travis.yml
recursive-include .travis *.sh
+recursive-include Icons *.png *.pdf
+
include Doc/Makefile
include Doc/make.bat
+include Doc/docs-requirements.txt
+include Doc/README.md
+include Doc/source/assets/img/favicon.ico
recursive-include Doc/man/man1 *.1
recursive-include Doc/source *.py *.rst
@@ -32,3 +39,5 @@ recursive-include Tests *.txt README
recursive-include Tests *.lwfn *.pfa *.pfb
recursive-include Tests *.xml *.designspace *.bin
recursive-include Tests *.afm
+recursive-include Tests *.json
+recursive-include Tests *.ufoz
diff --git a/METADATA b/METADATA
index 00ee9d74..6ef136d1 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,6 @@
+# *** THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS. PLEASE
+# CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE
+# DEPENDING ON IT IN YOUR PROJECT. ***
name: "fonttools"
description: "fontTools is a library for manipulating fonts, written in Python."
third_party {
@@ -7,12 +10,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://github.com/fonttools/fonttools/releases/download/3.44.0/fonttools-3.44.0.zip"
+ value: "https://github.com/fonttools/fonttools/archive/4.22.0.zip"
}
- version: "3.44.0"
+ version: "4.22.0"
+ license_type: BY_EXCEPTION_ONLY
last_upgrade_date {
- year: 2019
- month: 8
- day: 2
+ year: 2021
+ month: 4
+ day: 1
}
}
diff --git a/Makefile b/Makefile
index bd91d7f9..21cad6cd 100644
--- a/Makefile
+++ b/Makefile
@@ -14,9 +14,12 @@ uninstall:
pip uninstall --yes fonttools
check: all
- ./run-tests.sh
+ pytest
clean:
./setup.py clean --all
-.PHONY: all dist install install-user uninstall check clean
+docs:
+ cd Doc && $(MAKE) html
+
+.PHONY: all dist install install-user uninstall check clean docs
diff --git a/MetaTools/buildTableList.py b/MetaTools/buildTableList.py
index eb9fb858..c3766b98 100755
--- a/MetaTools/buildTableList.py
+++ b/MetaTools/buildTableList.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
import sys
import os
@@ -11,7 +11,7 @@ fontToolsDir = os.path.dirname(os.path.dirname(os.path.join(os.getcwd(), sys.arg
fontToolsDir= os.path.normpath(fontToolsDir)
tablesDir = os.path.join(fontToolsDir,
"Lib", "fontTools", "ttLib", "tables")
-docFile = os.path.join(fontToolsDir, "README.rst")
+docFile = os.path.join(fontToolsDir, "Doc/source/ttx.rst")
names = glob.glob1(tablesDir, "*.py")
@@ -33,9 +33,6 @@ tables.sort()
with open(os.path.join(tablesDir, "__init__.py"), "w") as file:
file.write('''
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
# DON'T EDIT! This file is generated by MetaTools/buildTableList.py.
def _moduleFinderHint():
"""Dummy function to let modulefinder know what tables may be
@@ -55,7 +52,7 @@ if __name__ == "__main__":
''')
-begin = ".. begin table list\n.. code::\n"
+begin = ".. begin table list\n"
end = ".. end table list"
with open(docFile) as f:
doc = f.read()
@@ -65,9 +62,10 @@ beginPos = beginPos + len(begin) + 1
endPos = doc.find(end)
lines = textwrap.wrap(", ".join(tables[:-1]) + " and " + tables[-1], 66)
+intro = "The following tables are currently supported::\n\n"
blockquote = "\n".join(" "*4 + line for line in lines) + "\n"
-doc = doc[:beginPos] + blockquote + doc[endPos:]
+doc = doc[:beginPos] + intro + blockquote + "\n" + doc[endPos:]
with open(docFile, "w") as f:
f.write(doc)
diff --git a/MetaTools/buildUCD.py b/MetaTools/buildUCD.py
index 12bd58f1..16ae150a 100755
--- a/MetaTools/buildUCD.py
+++ b/MetaTools/buildUCD.py
@@ -1,10 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""
Tools to parse data files from the Unicode Character Database.
"""
-from __future__ import print_function, absolute_import, division
-from __future__ import unicode_literals
try:
from urllib.request import urlopen
diff --git a/MetaTools/roundTrip.py b/MetaTools/roundTrip.py
index 648bc9d9..f9094ab0 100755
--- a/MetaTools/roundTrip.py
+++ b/MetaTools/roundTrip.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""usage: ttroundtrip [options] font1 ... fontN
@@ -31,9 +31,9 @@ def usage():
def roundTrip(ttFile1, options, report):
fn = os.path.basename(ttFile1)
- xmlFile1 = tempfile.mktemp(".%s.ttx1" % fn)
- ttFile2 = tempfile.mktemp(".%s" % fn)
- xmlFile2 = tempfile.mktemp(".%s.ttx2" % fn)
+ xmlFile1 = tempfile.mkstemp(".%s.ttx1" % fn)
+ ttFile2 = tempfile.mkstemp(".%s" % fn)
+ xmlFile2 = tempfile.mkstemp(".%s.ttx2" % fn)
try:
ttx.ttDump(ttFile1, xmlFile1, options)
diff --git a/NEWS.rst b/NEWS.rst
index 2a00702a..b07f5b18 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -1,3 +1,553 @@
+4.22.0 (released 2021-04-01)
+----------------------------
+
+- [ttLib] Remove .Format from Coverage, ClassDef, SingleSubst, LigatureSubst,
+ AlternateSubst, MultipleSubst (#2238).
+ ATTENTION: This will change your TTX dumps!
+- [misc.arrayTools] move Vector to its own submodule, and rewrite as a tuple
+ subclass (#2201).
+- [docs] Added a terminology section for varLib (#2209).
+- [varLib] Move rounding to VariationModel, to avoid error accumulation from
+ multiple deltas (#2214)
+- [varLib] Explain merge errors in more human-friendly terms (#2223, #2226)
+- [otlLib] Correct some documentation (#2225)
+- [varLib/otlLib] Allow merging into VariationFont without first saving GPOS
+ PairPos2 (#2229)
+- [subset] Improve PairPosFormat2 subsetting (#2221)
+- [ttLib] TTFont.save: create file on disk as late as possible (#2253)
+- [cffLib] Add missing CFF2 dict operators LanguageGroup and ExpansionFactor
+ (#2249)
+ ATTENTION: This will change your TTX dumps!
+
+4.21.1 (released 2021-02-26)
+----------------------------
+
+- [pens] Reverted breaking change that turned ``AbstractPen`` and ``AbstractPointPen``
+ into abstract base classes (#2164, #2198).
+
+4.21.0 (released 2021-02-26)
+----------------------------
+
+- [feaLib] Indent anchor statements in ``asFea()`` to make them more legible and
+ diff-able (#2193).
+- [pens] Turn ``AbstractPen`` and ``AbstractPointPen`` into abstract base classes
+ (#2164).
+- [feaLib] Added support for parsing and building ``STAT`` table from AFDKO feature
+ files (#2039).
+- [instancer] Added option to update name table of generated instance using ``STAT``
+ table's axis values (#2189).
+- [bezierTools] Added functions to compute bezier point-at-time, as well as line-line,
+ curve-line and curve-curve intersections (#2192).
+
+4.20.0 (released 2021-02-15)
+----------------------------
+
+- [COLRv1] Added ``unbuildColrV1`` to deconstruct COLRv1 otTables to raw json-able
+ data structure; it does the reverse of ``buildColrV1`` (#2171).
+- [feaLib] Allow ``sub X by NULL`` sequence to delete a glyph (#2170).
+- [arrayTools] Fixed ``Vector`` division (#2173).
+- [COLRv1] Define new ``PaintSweepGradient`` (#2172).
+- [otTables] Moved ``Paint.Format`` enum class outside of ``Paint`` class definition,
+ now named ``PaintFormat``. It was clashing with paint instance ``Format`` attribute
+ and thus was breaking lazy load of COLR table which relies on magic ``__getattr__``
+ (#2175).
+- [COLRv1] Replace hand-coded builder functions with otData-driven dynamic
+ implementation (#2181).
+- [COLRv1] Define additional static (non-variable) Paint formats (#2181).
+- [subset] Added support for subsetting COLR v1 and CPAL tables (#2174, #2177).
+- [fontBuilder] Allow ``setupFvar`` to optionally take ``designspaceLib.AxisDescriptor``
+ objects. Added new ``setupAvar`` method. Support localised names for axes and
+ named instances (#2185).
+
+4.19.1 (released 2021-01-28)
+----------------------------
+
+- [woff2] An initial off-curve point with an overlap flag now stays an off-curve
+ point after compression.
+
+4.19.0 (released 2021-01-25)
+----------------------------
+
+- [codecs] Handle ``errors`` parameter different from 'strict' for the custom
+ extended mac encodings (#2137, #2132).
+- [featureVars] Raise better error message when a script is missing the required
+ default language system (#2154).
+- [COLRv1] Avoid abrupt change caused by rounding ``PaintRadialGradient.c0`` when
+ the start circle almost touches the end circle's perimeter (#2148).
+- [COLRv1] Support building unlimited lists of paints as 255-ary trees of
+ ``PaintColrLayers`` tables (#2153).
+- [subset] Prune redundant format-12 cmap subtables when all non-BMP characters
+ are dropped (#2146).
+- [basePen] Raise ``MissingComponentError`` instead of bare ``KeyError`` when a
+ referenced component is missing (#2145).
+
+4.18.2 (released 2020-12-16)
+----------------------------
+
+- [COLRv1] Implemented ``PaintTranslate`` paint format (#2129).
+- [varLib.cff] Fixed unbound local variable error (#1787).
+- [otlLib] Don't crash when creating OpenType class definitions if some glyphs
+ occur more than once (#2125).
+
+4.18.1 (released 2020-12-09)
+----------------------------
+
+- [colorLib] Speed optimization for ``LayerV1ListBuilder`` (#2119).
+- [mutator] Fixed missing tab in ``interpolate_cff2_metrics`` (0957dc7a).
+
+4.18.0 (released 2020-12-04)
+----------------------------
+
+- [COLRv1] Update to latest draft: added ``PaintRotate`` and ``PaintSkew`` (#2118).
+- [woff2] Support new ``brotlicffi`` bindings for PyPy (#2117).
+- [glifLib] Added ``expectContentsFile`` parameter to ``GlyphSet``, for use when
+ reading existing UFOs, to comply with the specification stating that a
+ ``contents.plist`` file must exist in a glyph set (#2114).
+- [subset] Allow ``LangSys`` tags in ``--layout-scripts`` option (#2112). For example:
+ ``--layout-scripts=arab.dflt,arab.URD,latn``; this will keep ``DefaultLangSys``
+ and ``URD`` language for ``arab`` script, and all languages for ``latn`` script.
+- [varLib.interpolatable] Allow UFOs to be checked; report open paths, non existant
+ glyphs; add a ``--json`` option to produce a machine-readable list of
+ incompatibilities
+- [pens] Added ``QuartzPen`` to create ``CGPath`` from glyph outlines on macOS.
+ Requires pyobjc (#2107).
+- [feaLib] You can export ``FONTTOOLS_LOOKUP_DEBUGGING=1`` to enable feature file
+ debugging info stored in ``Debg`` table (#2106).
+- [otlLib] Build more efficient format 1 and format 2 contextual lookups whenever
+ possible (#2101).
+
+4.17.1 (released 2020-11-16)
+----------------------------
+
+- [colorLib] Fixed regression in 4.17.0 when building COLR v0 table; when color
+ layers are stored in UFO lib plist, we can't distinguish tuples from lists so
+ we need to accept either types (e5439eb9, googlefonts/ufo2ft/issues#426).
+
+4.17.0 (released 2020-11-12)
+----------------------------
+
+- [colorLib/otData] Updated to latest draft ``COLR`` v1 spec (#2092).
+- [svgLib] Fixed parsing error when arc commands' boolean flags are not separated
+ by space or comma (#2094).
+- [varLib] Interpret empty non-default glyphs as 'missing', if the default glyph is
+ not empty (#2082).
+- [feaLib.builder] Only stash lookup location for ``Debg`` if ``Builder.buildLookups_``
+ has cooperated (#2065, #2067).
+- [varLib] Fixed bug in VarStore optimizer (#2073, #2083).
+- [varLib] Add designspace lib key for custom feavar feature tag (#2080).
+- Add HashPointPen adapted from psautohint. With this pen, a hash value of a glyph
+ can be computed, which can later be used to detect glyph changes (#2005).
+
+4.16.1 (released 2020-10-05)
+----------------------------
+
+- [varLib.instancer] Fixed ``TypeError`` exception when instantiating a VF with
+ a GSUB table 1.1 in which ``FeatureVariations`` attribute is present but set to
+ ``None`` -- indicating that optional ``FeatureVariations`` is missing (#2077).
+- [glifLib] Make ``x`` and ``y`` attributes of the ``point`` element required
+ even when validation is turned off, and raise a meaningful ``GlifLibError``
+ message when that happens (#2075).
+
+4.16.0 (released 2020-09-30)
+----------------------------
+
+- [removeOverlaps] Added new module and ``removeOverlaps`` function that merges
+ overlapping contours and components in TrueType glyphs. It requires the
+ `skia-pathops <https://github.com/fonttools/skia-pathops>`__ module.
+ Note that removing overlaps invalidates the TrueType hinting (#2068).
+- [varLib.instancer] Added ``--remove-overlaps`` command-line option.
+ The ``overlap`` option in ``instantiateVariableFont`` now takes an ``OverlapMode``
+ enum: 0: KEEP_AND_DONT_SET_FLAGS, 1: KEEP_AND_SET_FLAGS (default), and 2: REMOVE.
+ The latter is equivalent to calling ``removeOverlaps`` on the generated static
+ instance. The option continues to accept ``bool`` value for backward compatibility.
+
+
+4.15.0 (released 2020-09-21)
+----------------------------
+
+- [plistlib] Added typing annotations to plistlib module. Set up mypy static
+ typechecker to run automatically on CI (#2061).
+- [ttLib] Implement private ``Debg`` table, a reverse-DNS namespaced JSON dict.
+- [feaLib] Optionally add an entry into the ``Debg`` table with the original
+ lookup name (if any), feature name / script / language combination (if any),
+ and original source filename and line location. Annotate the ttx output for
+ a lookup with the information from the Debg table (#2052).
+- [sfnt] Disabled checksum checking by default in ``SFNTReader`` (#2058).
+- [Docs] Document ``mtiLib`` module (#2027).
+- [varLib.interpolatable] Added checks for contour node count and operation type
+ of each node (#2054).
+- [ttLib] Added API to register custom table packer/unpacker classes (#2055).
+
+4.14.0 (released 2020-08-19)
+----------------------------
+
+- [feaLib] Allow anonymous classes in LookupFlags definitions (#2037).
+- [Docs] Better document DesignSpace rules processing order (#2041).
+- [ttLib] Fixed 21-year old bug in ``maxp.maxComponentDepth`` calculation (#2044,
+ #2045).
+- [varLib.models] Fixed misspelled argument name in CLI entry point (81d0042a).
+- [subset] When subsetting GSUB v1.1, fixed TypeError by checking whether the
+ optional FeatureVariations table is present (e63ecc5b).
+- [Snippets] Added snippet to show how to decompose glyphs in a TTF (#2030).
+- [otlLib] Generate GSUB type 5 and GPOS type 7 contextual lookups where appropriate
+ (#2016).
+
+4.13.0 (released 2020-07-10)
+----------------------------
+
+- [feaLib/otlLib] Moved lookup subtable builders from feaLib to otlLib; refactored
+ some common code (#2004, #2007).
+- [docs] Document otlLib module (#2009).
+- [glifLib] Fixed bug with some UFO .glif filenames clashing on case-insensitive
+ filesystems (#2001, #2002).
+- [colorLib] Updated COLRv1 implementation following changes in the draft spec:
+ (#2008, googlefonts/colr-gradients-spec#24).
+
+4.12.1 (released 2020-06-16)
+----------------------------
+
+- [_n_a_m_e] Fixed error in ``addMultilingualName`` with one-character names.
+ Only attempt to recovered malformed UTF-16 data from a ``bytes`` string,
+ not from unicode ``str`` (#1997, #1998).
+
+4.12.0 (released 2020-06-09)
+----------------------------
+
+- [otlLib/varLib] Ensure that the ``AxisNameID`` in the ``STAT`` and ``fvar``
+ tables is grater than 255 as per OpenType spec (#1985, #1986).
+- [docs] Document more modules in ``fontTools.misc`` package: ``filenames``,
+ ``fixedTools``, ``intTools``, ``loggingTools``, ``macCreatorType``, ``macRes``,
+ ``plistlib`` (#1981).
+- [OS/2] Don't calculate whole sets of unicode codepoints, use faster and more memory
+ efficient ranges and bisect lookups (#1984).
+- [voltLib] Support writing back abstract syntax tree as VOLT data (#1983).
+- [voltLib] Accept DO_NOT_TOUCH_CMAP keyword (#1987).
+- [subset/merge] Fixed a namespace clash involving a private helper class (#1955).
+
+4.11.0 (released 2020-05-28)
+----------------------------
+
+- [feaLib] Introduced ``includeDir`` parameter on Parser and IncludingLexer to
+ explicitly specify the directory to search when ``include()`` statements are
+ encountered (#1973).
+- [ufoLib] Silently delete duplicate glyphs within the same kerning group when reading
+ groups (#1970).
+- [ttLib] Set version of COLR table when decompiling COLRv1 (commit 9d8a7e2).
+
+4.10.2 (released 2020-05-20)
+----------------------------
+
+- [sfnt] Fixed ``NameError: SimpleNamespace`` while reading TTC header. The regression
+ was introduced with 4.10.1 after removing ``py23`` star import.
+
+4.10.1 (released 2020-05-19)
+----------------------------
+
+- [sfnt] Make ``SFNTReader`` pickleable even when TTFont is loaded with lazy=True
+ option and thus keeps a reference to an external file (#1962, #1967).
+- [feaLib.ast] Restore backward compatibility (broken in 4.10 with #1905) for
+ ``ChainContextPosStatement`` and ``ChainContextSubstStatement`` classes.
+ Make them accept either list of lookups or list of lists of lookups (#1961).
+- [docs] Document some modules in ``fontTools.misc`` package: ``arrayTools``,
+ ``bezierTools`` ``cliTools`` and ``eexec`` (#1956).
+- [ttLib._n_a_m_e] Fixed ``findMultilingualName()`` when name record's ``string`` is
+ encoded as bytes sequence (#1963).
+
+4.10.0 (released 2020-05-15)
+----------------------------
+
+- [varLib] Allow feature variations to be active across the entire space (#1957).
+- [ufoLib] Added support for ``formatVersionMinor`` in UFO's ``fontinfo.plist`` and for
+ ``formatMinor`` attribute in GLIF file as discussed in unified-font-object/ufo-spec#78.
+ No changes in reading or writing UFOs until an upcoming (non-0) minor update of the
+ UFO specification is published (#1786).
+- [merge] Fixed merging fonts with different versions of ``OS/2`` table (#1865, #1952).
+- [subset] Fixed ``AttributeError`` while subsetting ``ContextSubst`` and ``ContextPos``
+ Format 3 subtable (#1879, #1944).
+- [ttLib.table._m_e_t_a] if data happens to be ascii, emit comment in TTX (#1938).
+- [feaLib] Support multiple lookups per glyph position (#1905).
+- [psCharStrings] Use inheritance to avoid repeated code in initializer (#1932).
+- [Doc] Improved documentation for the following modules: ``afmLib`` (#1933), ``agl``
+ (#1934), ``cffLib`` (#1935), ``cu2qu`` (#1937), ``encodings`` (#1940), ``feaLib``
+ (#1941), ``merge`` (#1949).
+- [Doc] Split off developer-centric info to new page, making front page of docs more
+ user-focused. List all utilities and sub-modules with brief descriptions.
+ Make README more concise and focused (#1914).
+- [otlLib] Add function to build STAT table from high-level description (#1926).
+- [ttLib._n_a_m_e] Add ``findMultilingualName()`` method (#1921).
+- [unicodedata] Update ``RTL_SCRIPTS`` for Unicode 13.0 (#1925).
+- [gvar] Sort ``gvar`` XML output by glyph name, not glyph order (#1907, #1908).
+- [Doc] Added help options to ``fonttools`` command line tool (#1913, #1920).
+ Ensure all fonttools CLI tools have help documentation (#1948).
+- [ufoLib] Only write fontinfo.plist when there actually is content (#1911).
+
+4.9.0 (released 2020-04-29)
+---------------------------
+
+- [subset] Fixed subsetting of FeatureVariations table. The subsetter no longer drops
+ FeatureVariationRecords that have empty substitutions as that will keep the search
+ going and thus change the logic. It will only drop empty records that occur at the
+ end of the FeatureVariationRecords array (#1881).
+- [subset] Remove FeatureVariations table and downgrade GSUB/GPOS to version 0x10000
+ when FeatureVariations contain no FeatureVariationRecords after subsetting (#1903).
+- [agl] Add support for legacy Adobe Glyph List of glyph names in ``fontTools.agl``
+ (#1895).
+- [feaLib] Ignore superfluous script statements (#1883).
+- [feaLib] Hide traceback by default on ``fonttools feaLib`` command line.
+ Use ``--traceback`` option to show (#1898).
+- [feaLib] Check lookup index in chaining sub/pos lookups and print better error
+ message (#1896, #1897).
+- [feaLib] Fix building chained alt substitutions (#1902).
+- [Doc] Included all fontTools modules in the sphinx-generated documentation, and
+ published it to ReadTheDocs for continuous documentation of the fontTools project
+ (#1333). Check it out at https://fonttools.readthedocs.io/. Thanks to Chris Simpkins!
+- [transform] The ``Transform`` class is now subclass of ``typing.NamedTuple``. No
+ change in functionality (#1904).
+
+
+4.8.1 (released 2020-04-17)
+---------------------------
+
+- [feaLib] Fixed ``AttributeError: 'NoneType' has no attribute 'getAlternateGlyphs'``
+ when ``aalt`` feature references a chain contextual substitution lookup
+ (googlefonts/fontmake#648, #1878).
+
+4.8.0 (released 2020-04-16)
+---------------------------
+
+- [feaLib] If Parser is initialized without a ``glyphNames`` parameter, it cannot
+ distinguish between a glyph name containing an hyphen, or a range of glyph names;
+ instead of raising an error, it now interprets them as literal glyph names, while
+ also outputting a logging warning to alert user about the ambiguity (#1768, #1870).
+- [feaLib] When serializing AST to string, emit spaces around hyphens that denote
+ ranges. Also, fixed an issue with CID ranges when round-tripping AST->string->AST
+ (#1872).
+- [Snippets/otf2ttf] In otf2ttf.py script update LSB in hmtx to match xMin (#1873).
+- [colorLib] Added experimental support for building ``COLR`` v1 tables as per
+ the `colr-gradients-spec <https://github.com/googlefonts/colr-gradients-spec/blob/master/colr-gradients-spec.md>`__
+ draft proposal. **NOTE**: both the API and the XML dump of ``COLR`` v1 are
+ susceptible to change while the proposal is being discussed and formalized (#1822).
+
+4.7.0 (released 2020-04-03)
+---------------------------
+
+- [cu2qu] Added ``fontTools.cu2qu`` package, imported from the original
+ `cu2qu <https://github.com/googlefonts/cu2qu>`__ project. The ``cu2qu.pens`` module
+ was moved to ``fontTools.pens.cu2quPen``. The optional cu2qu extension module
+ can be compiled by installing `Cython <https://cython.org/>`__ before installing
+ fonttools from source (i.e. git repo or sdist tarball). The wheel package that
+ is published on PyPI (i.e. the one ``pip`` downloads, unless ``--no-binary``
+ option is used), will continue to be pure-Python for now (#1868).
+
+4.6.0 (released 2020-03-24)
+---------------------------
+
+- [varLib] Added support for building variable ``BASE`` table version 1.1 (#1858).
+- [CPAL] Added ``fromRGBA`` method to ``Color`` class (#1861).
+
+
+4.5.0 (released 2020-03-20)
+---------------------------
+
+- [designspaceLib] Added ``add{Axis,Source,Instance,Rule}Descriptor`` methods to
+ ``DesignSpaceDocument`` class, to initialize new descriptor objects using keyword
+ arguments, and at the same time append them to the current document (#1860).
+- [unicodedata] Update to Unicode 13.0 (#1859).
+
+4.4.3 (released 2020-03-13)
+---------------------------
+
+- [varLib] Always build ``gvar`` table for TrueType-flavored Variable Fonts,
+ even if it contains no variation data. The table is required according to
+ the OpenType spec (#1855, #1857).
+
+4.4.2 (released 2020-03-12)
+---------------------------
+
+- [ttx] Annotate ``LookupFlag`` in XML dump with comment explaining what bits
+ are set and what they mean (#1850).
+- [feaLib] Added more descriptive message to ``IncludedFeaNotFound`` error (#1842).
+
+4.4.1 (released 2020-02-26)
+---------------------------
+
+- [woff2] Skip normalizing ``glyf`` and ``loca`` tables if these are missing from
+ a font (e.g. in NotoColorEmoji using ``CBDT/CBLC`` tables).
+- [timeTools] Use non-localized date parsing in ``timestampFromString``, to fix
+ error when non-English ``LC_TIME`` locale is set (#1838, #1839).
+- [fontBuilder] Make sure the CFF table generated by fontBuilder can be used by varLib
+ without having to compile and decompile the table first. This was breaking in
+ converting the CFF table to CFF2 due to some unset attributes (#1836).
+
+4.4.0 (released 2020-02-18)
+---------------------------
+
+- [colorLib] Added ``fontTools.colorLib.builder`` module, initially with ``buildCOLR``
+ and ``buildCPAL`` public functions. More color font formats will follow (#1827).
+- [fontBuilder] Added ``setupCOLR`` and ``setupCPAL`` methods (#1826).
+- [ttGlyphPen] Quantize ``GlyphComponent.transform`` floats to ``F2Dot14`` to fix
+ round-trip issue when computing bounding boxes of transformed components (#1830).
+- [glyf] If a component uses reference points (``firstPt`` and ``secondPt``) for
+ alignment (instead of X and Y offsets), compute the effective translation offset
+ *after* having applied any transform (#1831).
+- [glyf] When all glyphs have zero contours, compile ``glyf`` table data as a single
+ null byte in order to pass validation by OTS and Windows (#1829).
+- [feaLib] Parsing feature code now ensures that referenced glyph names are part of
+ the known glyph set, unless a glyph set was not provided.
+- [varLib] When filling in the default axis value for a missing location of a source or
+ instance, correctly map the value forward.
+- [varLib] The avar table can now contain mapping output values that are greater than
+ OR EQUAL to the preceeding value, as the avar specification allows this.
+- [varLib] The errors of the module are now ordered hierarchically below VarLibError.
+ See #1821.
+
+4.3.0 (released 2020-02-03)
+---------------------------
+
+- [EBLC/CBLC] Fixed incorrect padding length calculation for Format 3 IndexSubTable
+ (#1817, #1818).
+- [varLib] Fixed error when merging OTL tables and TTFonts were loaded as ``lazy=True``
+ (#1808, #1809).
+- [varLib] Allow to use master fonts containing ``CFF2`` table when building VF (#1816).
+- [ttLib] Make ``recalcBBoxes`` option work also with ``CFF2`` table (#1816).
+- [feaLib] Don't reset ``lookupflag`` in lookups defined inside feature blocks.
+ They will now inherit the current ``lookupflag`` of the feature. This is what
+ Adobe ``makeotf`` also does in this case (#1815).
+- [feaLib] Fixed bug with mixed single/multiple substitutions. If a single substitution
+ involved a glyph class, we were incorrectly using only the first glyph in the class
+ (#1814).
+
+4.2.5 (released 2020-01-29)
+---------------------------
+
+- [feaLib] Do not fail on duplicate multiple substitutions, only warn (#1811).
+- [subset] Optimize SinglePos subtables to Format 1 if all ValueRecords are the same
+ (#1802).
+
+4.2.4 (released 2020-01-09)
+---------------------------
+
+- [unicodedata] Update RTL_SCRIPTS for Unicode 11 and 12.
+
+4.2.3 (released 2020-01-07)
+---------------------------
+
+- [otTables] Fixed bug when splitting `MarkBasePos` subtables as offsets overflow.
+ The mark class values in the split subtable were not being updated, leading to
+ invalid mark-base attachments (#1797, googlefonts/noto-source#145).
+- [feaLib] Only log a warning instead of error when features contain duplicate
+ substitutions (#1767).
+- [glifLib] Strip XML comments when parsing with lxml (#1784, #1785).
+
+4.2.2 (released 2019-12-12)
+---------------------------
+
+- [subset] Fixed issue with subsetting FeatureVariations table when the index
+ of features changes as features get dropped. The feature index need to be
+ remapped to point to index of the remaining features (#1777, #1782).
+- [fontBuilder] Added `addFeatureVariations` method to `FontBuilder` class. This
+ is a shorthand for calling `featureVars.addFeatureVariations` on the builder's
+ TTFont object (#1781).
+- [glyf] Fixed the flags bug in glyph.drawPoints() like we did for glyph.draw()
+ (#1771, #1774).
+
+4.2.1 (released 2019-12-06)
+---------------------------
+
+- [glyf] Use the ``flagOnCurve`` bit mask in ``glyph.draw()``, so that we ignore
+ the ``overlap`` flag that may be set when instantiating variable fonts (#1771).
+
+4.2.0 (released 2019-11-28)
+---------------------------
+
+- [pens] Added the following pens:
+
+ * ``roundingPen.RoundingPen``: filter pen that rounds coordinates and components'
+ offsets to integer;
+ * ``roundingPen.RoundingPointPen``: like the above, but using PointPen protocol.
+ * ``filterPen.FilterPointPen``: base class for filter point pens;
+ * ``transformPen.TransformPointPen``: filter point pen to apply affine transform;
+ * ``recordingPen.RecordingPointPen``: records and replays point-pen commands.
+
+- [ttGlyphPen] Always round float coordinates and component offsets to integers
+ (#1763).
+- [ufoLib] When converting kerning groups from UFO2 to UFO3, avoid confusing
+ groups with the same name as one of the glyphs (#1761, #1762,
+ unified-font-object/ufo-spec#98).
+
+4.1.0 (released 2019-11-18)
+---------------------------
+
+- [instancer] Implemented restricting axis ranges (level 3 partial instancing).
+ You can now pass ``{axis_tag: (min, max)}`` tuples as input to the
+ ``instantiateVariableFont`` function. Note that changing the default axis
+ position is not supported yet. The command-line script also accepts axis ranges
+ in the form of colon-separated float values, e.g. ``wght=400:700`` (#1753, #1537).
+- [instancer] Never drop STAT ``DesignAxis`` records, but only prune out-of-range
+ ``AxisValue`` records.
+- [otBase/otTables] Enforce that VarStore.RegionAxisCount == fvar.axisCount, even
+ when regions list is empty to appease OTS < v8.0 (#1752).
+- [designspaceLib] Defined new ``processing`` attribute for ``<rules>`` element,
+ with values "first" or "last", plus other editorial changes to DesignSpace
+ specification. Bumped format version to 4.1 (#1750).
+- [varLib] Improved error message when masters' glyph orders do not match (#1758,
+ #1759).
+- [featureVars] Allow to specify custom feature tag in ``addFeatureVariations``;
+ allow said feature to already exist, in which case we append new lookup indices
+ to existing features. Implemented ``<rules>`` attribute ``processing`` according to
+ DesignSpace specification update in #1750. Depending on this flag, we generate
+ either an 'rvrn' (always processed first) or a 'rclt' feature (follows lookup order,
+ therefore last) (#1747, #1625, #1371).
+- [ttCollection] Added support for context manager auto-closing via ``with`` statement
+ like with ``TTFont`` (#1751).
+- [unicodedata] Require unicodedata2 >= 12.1.0.
+- [py2.py3] Removed yet more PY2 vestiges (#1743).
+- [_n_a_m_e] Fixed issue when comparing NameRecords with different string types (#1742).
+- [fixedTools] Changed ``fixedToFloat`` to not do any rounding but simply return
+ ``value / (1 << precisionBits)``. Added ``floatToFixedToStr`` and
+ ``strToFixedToFloat`` functions to be used when loading from or dumping to XML.
+ Fixed values (e.g. fvar axes and instance coordinates, avar mappings, etc.) are
+ are now stored as un-rounded decimal floats upon decompiling (#1740, #737).
+- [feaLib] Fixed handling of multiple ``LigatureCaret`` statements for the same glyph.
+ Only the first rule per glyph is used, additional ones are ignored (#1733).
+
+4.0.2 (released 2019-09-26)
+---------------------------
+
+- [voltLib] Added support for ``ALL`` and ``NONE`` in ``PROCESS_MARKS`` (#1732).
+- [Silf] Fixed issue in ``Silf`` table compilation and decompilation regarding str vs
+ bytes in python3 (#1728).
+- [merge] Handle duplicate glyph names better: instead of appending font index to
+ all glyph names, use similar code like we use in ``post`` and ``CFF`` tables (#1729).
+
+4.0.1 (released 2019-09-11)
+---------------------------
+
+- [otTables] Support fixing offset overflows in ``MultipleSubst`` lookup subtables
+ (#1706).
+- [subset] Prune empty strikes in ``EBDT`` and ``CBDT`` table data (#1698, #1633).
+- [pens] Fixed issue in ``PointToSegmentPen`` when last point of closed contour has
+ same coordinates as the starting point and was incorrectly dropped (#1720).
+- [Graphite] Fixed ``Sill`` table output to pass OTS (#1705).
+- [name] Added ``removeNames`` method to ``table__n_a_m_e`` class (#1719).
+- [ttLib] Added aliases for renamed entries ``ascender`` and ``descender`` in
+ ``hhea`` table (#1715).
+
+4.0.0 (released 2019-08-22)
+---------------------------
+
+- NOTE: The v4.x version series only supports Python 3.6 or greater. You can keep
+ using fonttools 3.x if you need support for Python 2.
+- [py23] Removed all the python2-only code since it is no longer reachable, thus
+ unused; only the Python3 symbols were kept, but these are no-op. The module is now
+ DEPRECATED and will removed in the future.
+- [ttLib] Fixed UnboundLocalError for empty loca/glyph tables (#1680). Also, allow
+ the glyf table to be incomplete when dumping to XML (#1681).
+- [varLib.models] Fixed KeyError while sorting masters and there are no on-axis for
+ a given axis (38a8eb0e).
+- [cffLib] Make sure glyph names are unique (#1699).
+- [feaLib] Fix feature parser to correctly handle octal numbers (#1700).
+
3.44.0 (released 2019-08-02)
----------------------------
diff --git a/PKG-INFO b/PKG-INFO
deleted file mode 100644
index 29e94678..00000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,1823 +0,0 @@
-Metadata-Version: 2.1
-Name: fonttools
-Version: 3.44.0
-Summary: Tools to manipulate font files
-Home-page: http://github.com/fonttools/fonttools
-Author: Just van Rossum
-Author-email: just@letterror.com
-Maintainer: Behdad Esfahbod
-Maintainer-email: behdad@behdad.org
-License: MIT
-Description: |Travis Build Status| |Appveyor Build status| |Coverage Status| |PyPI| |Gitter Chat|
-
- What is this?
- ~~~~~~~~~~~~~
-
- | fontTools is a library for manipulating fonts, written in Python. The
- project includes the TTX tool, that can convert TrueType and OpenType
- fonts to and from an XML text format, which is also called TTX. It
- supports TrueType, OpenType, AFM and to an extent Type 1 and some
- Mac-specific formats. The project has an `MIT open-source
- licence <LICENSE>`__.
- | Among other things this means you can use it free of charge.
-
- Installation
- ~~~~~~~~~~~~
-
- FontTools requires `Python <http://www.python.org/download/>`__ 2.7, 3.4
- or later.
-
- **NOTE** From August 2019, until no later than January 1 2020, the support
- for *Python 2.7* will be limited to only critical bug fixes, and no new features
- will be added to the ``py27`` branch. The upcoming FontTools 4.x series will require
- *Python 3.6* or above. You can read more `here <https://python3statement.org>`__
- and `here <https://github.com/fonttools/fonttools/issues/765>`__ for the
- reasons behind this decision.
-
- The package is listed in the Python Package Index (PyPI), so you can
- install it with `pip <https://pip.pypa.io>`__:
-
- .. code:: sh
-
- pip install fonttools
-
- If you would like to contribute to its development, you can clone the
- repository from GitHub, install the package in 'editable' mode and
- modify the source code in place. We recommend creating a virtual
- environment, using `virtualenv <https://virtualenv.pypa.io>`__ or
- Python 3 `venv <https://docs.python.org/3/library/venv.html>`__ module.
-
- .. code:: sh
-
- # download the source code to 'fonttools' folder
- git clone https://github.com/fonttools/fonttools.git
- cd fonttools
-
- # create new virtual environment called e.g. 'fonttools-venv', or anything you like
- python -m virtualenv fonttools-venv
-
- # source the `activate` shell script to enter the environment (Un*x); to exit, just type `deactivate`
- . fonttools-venv/bin/activate
-
- # to activate the virtual environment in Windows `cmd.exe`, do
- fonttools-venv\Scripts\activate.bat
-
- # install in 'editable' mode
- pip install -e .
-
- TTX – From OpenType and TrueType to XML and Back
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Once installed you can use the ``ttx`` command to convert binary font
- files (``.otf``, ``.ttf``, etc) to the TTX XML format, edit them, and
- convert them back to binary format. TTX files have a .ttx file
- extension.
-
- .. code:: sh
-
- ttx /path/to/font.otf
- ttx /path/to/font.ttx
-
- The TTX application can be used in two ways, depending on what
- platform you run it on:
-
- - As a command line tool (Windows/DOS, Unix, macOS)
- - By dropping files onto the application (Windows, macOS)
-
- TTX detects what kind of files it is fed: it will output a ``.ttx`` file
- when it sees a ``.ttf`` or ``.otf``, and it will compile a ``.ttf`` or
- ``.otf`` when the input file is a ``.ttx`` file. By default, the output
- file is created in the same folder as the input file, and will have the
- same name as the input file but with a different extension. TTX will
- *never* overwrite existing files, but if necessary will append a unique
- number to the output filename (before the extension) such as
- ``Arial#1.ttf``
-
- When using TTX from the command line there are a bunch of extra options.
- These are explained in the help text, as displayed when typing
- ``ttx -h`` at the command prompt. These additional options include:
-
- - specifying the folder where the output files are created
- - specifying which tables to dump or which tables to exclude
- - merging partial ``.ttx`` files with existing ``.ttf`` or ``.otf``
- files
- - listing brief table info instead of dumping to ``.ttx``
- - splitting tables to separate ``.ttx`` files
- - disabling TrueType instruction disassembly
-
- The TTX file format
- -------------------
-
- The following tables are currently supported:
-
- .. begin table list
- .. code::
-
- BASE, CBDT, CBLC, CFF, CFF2, COLR, CPAL, DSIG, EBDT, EBLC, FFTM,
- Feat, GDEF, GMAP, GPKG, GPOS, GSUB, Glat, Gloc, HVAR, JSTF, LTSH,
- MATH, META, MVAR, OS/2, SING, STAT, SVG, Silf, Sill, TSI0, TSI1,
- TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS, TSIV, TTFA, VDMX,
- VORG, VVAR, ankr, avar, bsln, cidg, cmap, cvar, cvt, feat, fpgm,
- fvar, gasp, gcid, glyf, gvar, hdmx, head, hhea, hmtx, kern, lcar,
- loca, ltag, maxp, meta, mort, morx, name, opbd, post, prep, prop,
- sbix, trak, vhea and vmtx
- .. end table list
-
- Other tables are dumped as hexadecimal data.
-
- TrueType fonts use glyph indices (GlyphIDs) to refer to glyphs in most
- places. While this is fine in binary form, it is really hard to work
- with for humans. Therefore we use names instead.
-
- The glyph names are either extracted from the ``CFF`` table or the
- ``post`` table, or are derived from a Unicode ``cmap`` table. In the
- latter case the Adobe Glyph List is used to calculate names based on
- Unicode values. If all of these methods fail, names are invented based
- on GlyphID (eg ``glyph00142``)
-
- It is possible that different glyphs use the same name. If this happens,
- we force the names to be unique by appending ``#n`` to the name (``n``
- being an integer number.) The original names are being kept, so this has
- no influence on a "round tripped" font.
-
- Because the order in which glyphs are stored inside the binary font is
- important, we maintain an ordered list of glyph names in the font.
-
- Other Tools
- ~~~~~~~~~~~
-
- Commands for merging and subsetting fonts are also available:
-
- .. code:: sh
-
- pyftmerge
- pyftsubset
-
- fontTools Python Module
- ~~~~~~~~~~~~~~~~~~~~~~~
-
- The fontTools Python module provides a convenient way to
- programmatically edit font files.
-
- .. code:: py
-
- >>> from fontTools.ttLib import TTFont
- >>> font = TTFont('/path/to/font.ttf')
- >>> font
- <fontTools.ttLib.TTFont object at 0x10c34ed50>
- >>>
-
- A selection of sample Python programs is in the
- `Snippets <https://github.com/fonttools/fonttools/blob/master/Snippets/>`__
- directory.
-
- Optional Requirements
- ---------------------
-
- The ``fontTools`` package currently has no (required) external dependencies
- besides the modules included in the Python Standard Library.
- However, a few extra dependencies are required by some of its modules, which
- are needed to unlock optional features.
- The ``fonttools`` PyPI distribution also supports so-called "extras", i.e. a
- set of keywords that describe a group of additional dependencies, which can be
- used when installing via pip, or when specifying a requirement.
- For example:
-
- .. code:: sh
-
- pip install fonttools[ufo,lxml,woff,unicode]
-
- This command will install fonttools, as well as the optional dependencies that
- are required to unlock the extra features named "ufo", etc.
-
- - ``Lib/fontTools/misc/etree.py``
-
- The module exports a ElementTree-like API for reading/writing XML files, and
- allows to use as the backend either the built-in ``xml.etree`` module or
- `lxml <https://http://lxml.de>`__. The latter is preferred whenever present,
- as it is generally faster and more secure.
-
- *Extra:* ``lxml``
-
- - ``Lib/fontTools/ufoLib``
-
- Package for reading and writing UFO source files; it requires:
-
- * `fs <https://pypi.org/pypi/fs>`__: (aka ``pyfilesystem2``) filesystem
- abstraction layer.
-
- * `enum34 <https://pypi.org/pypi/enum34>`__: backport for the built-in ``enum``
- module (only required on Python < 3.4).
-
- *Extra:* ``ufo``
-
- - ``Lib/fontTools/ttLib/woff2.py``
-
- Module to compress/decompress WOFF 2.0 web fonts; it requires:
-
- * `brotli <https://pypi.python.org/pypi/Brotli>`__: Python bindings of
- the Brotli compression library.
-
- *Extra:* ``woff``
-
- - ``Lib/fontTools/ttLib/sfnt.py``
-
- To better compress WOFF 1.0 web fonts, the following module can be used
- instead of the built-in ``zlib`` library:
-
- * `zopfli <https://pypi.python.org/pypi/zopfli>`__: Python bindings of
- the Zopfli compression library.
-
- *Extra:* ``woff``
-
- - ``Lib/fontTools/unicode.py``
-
- To display the Unicode character names when dumping the ``cmap`` table
- with ``ttx`` we use the ``unicodedata`` module in the Standard Library.
- The version included in there varies between different Python versions.
- To use the latest available data, you can install:
-
- * `unicodedata2 <https://pypi.python.org/pypi/unicodedata2>`__:
- ``unicodedata`` backport for Python 2.7 and 3.x updated to the latest
- Unicode version 12.0. Note this is not necessary if you use Python 3.8
- as the latter already comes with an up-to-date ``unicodedata``.
-
- *Extra:* ``unicode``
-
- - ``Lib/fontTools/varLib/interpolatable.py``
-
- Module for finding wrong contour/component order between different masters.
- It requires one of the following packages in order to solve the so-called
- "minimum weight perfect matching problem in bipartite graphs", or
- the Assignment problem:
-
- * `scipy <https://pypi.python.org/pypi/scipy>`__: the Scientific Library
- for Python, which internally uses `NumPy <https://pypi.python.org/pypi/numpy>`__
- arrays and hence is very fast;
- * `munkres <https://pypi.python.org/pypi/munkres>`__: a pure-Python
- module that implements the Hungarian or Kuhn-Munkres algorithm.
-
- *Extra:* ``interpolatable``
-
- - ``Lib/fontTools/varLib/plot.py``
-
- Module for visualizing DesignSpaceDocument and resulting VariationModel.
-
- * `matplotlib <https://pypi.org/pypi/matplotlib>`__: 2D plotting library.
-
- *Extra:* ``plot``
-
- - ``Lib/fontTools/misc/symfont.py``
-
- Advanced module for symbolic font statistics analysis; it requires:
-
- * `sympy <https://pypi.python.org/pypi/sympy>`__: the Python library for
- symbolic mathematics.
-
- *Extra:* ``symfont``
-
- - ``Lib/fontTools/t1Lib.py``
-
- To get the file creator and type of Macintosh PostScript Type 1 fonts
- on Python 3 you need to install the following module, as the old ``MacOS``
- module is no longer included in Mac Python:
-
- * `xattr <https://pypi.python.org/pypi/xattr>`__: Python wrapper for
- extended filesystem attributes (macOS platform only).
-
- *Extra:* ``type1``
-
- - ``Lib/fontTools/pens/cocoaPen.py``
-
- Pen for drawing glyphs with Cocoa ``NSBezierPath``, requires:
-
- * `PyObjC <https://pypi.python.org/pypi/pyobjc>`__: the bridge between
- Python and the Objective-C runtime (macOS platform only).
-
- - ``Lib/fontTools/pens/qtPen.py``
-
- Pen for drawing glyphs with Qt's ``QPainterPath``, requires:
-
- * `PyQt5 <https://pypi.python.org/pypi/PyQt5>`__: Python bindings for
- the Qt cross platform UI and application toolkit.
-
- - ``Lib/fontTools/pens/reportLabPen.py``
-
- Pen to drawing glyphs as PNG images, requires:
-
- * `reportlab <https://pypi.python.org/pypi/reportlab>`__: Python toolkit
- for generating PDFs and graphics.
-
- Testing
- ~~~~~~~
-
- To run the test suite, you need to install `pytest <http://docs.pytest.org/en/latest/>`__.
- When you run the ``pytest`` command, the tests will run against the
- installed ``fontTools`` package, or the first one found in the
- ``PYTHONPATH``.
-
- You can also use `tox <https://tox.readthedocs.io/en/latest/>`__ to
- automatically run tests on different Python versions in isolated virtual
- environments.
-
- .. code:: sh
-
- pip install tox
- tox
-
- Note that when you run ``tox`` without arguments, the tests are executed
- for all the environments listed in tox.ini's ``envlist``. In our case,
- this includes Python 2.7 and 3.7, so for this to work the ``python2.7``
- and ``python3.7`` executables must be available in your ``PATH``.
-
- You can specify an alternative environment list via the ``-e`` option,
- or the ``TOXENV`` environment variable:
-
- .. code:: sh
-
- tox -e py27
- TOXENV="py36-cov,htmlcov" tox
-
- Development Community
- ~~~~~~~~~~~~~~~~~~~~~
-
- TTX/FontTools development is ongoing in an active community of
- developers, that includes professional developers employed at major
- software corporations and type foundries as well as hobbyists.
-
- Feature requests and bug reports are always welcome at
- https://github.com/fonttools/fonttools/issues/
-
- The best place for discussions about TTX from an end-user perspective as
- well as TTX/FontTools development is the
- https://groups.google.com/d/forum/fonttools mailing list. There is also
- a development https://groups.google.com/d/forum/fonttools-dev mailing
- list for continuous integration notifications. You can also email Behdad
- privately at behdad@behdad.org
-
- History
- ~~~~~~~
-
- The fontTools project was started by Just van Rossum in 1999, and was
- maintained as an open source project at
- http://sourceforge.net/projects/fonttools/. In 2008, Paul Wise (pabs3)
- began helping Just with stability maintenance. In 2013 Behdad Esfahbod
- began a friendly fork, thoroughly reviewing the codebase and making
- changes at https://github.com/behdad/fonttools to add new features and
- support for new font formats.
-
- Acknowledgements
- ~~~~~~~~~~~~~~~~
-
- In alphabetical order:
-
- Olivier Berten, Samyak Bhuta, Erik van Blokland, Petr van Blokland,
- Jelle Bosma, Sascha Brawer, Tom Byrer, Frédéric Coiffier, Vincent
- Connare, Dave Crossland, Simon Daniels, Peter Dekkers, Behdad Esfahbod,
- Behnam Esfahbod, Hannes Famira, Sam Fishman, Matt Fontaine, Yannis
- Haralambous, Greg Hitchcock, Jeremie Hornus, Khaled Hosny, John Hudson,
- Denis Moyogo Jacquerye, Jack Jansen, Tom Kacvinsky, Jens Kutilek,
- Antoine Leca, Werner Lemberg, Tal Leming, Peter Lofting, Cosimo Lupo,
- Masaya Nakamura, Dave Opstad, Laurence Penney, Roozbeh Pournader, Garret
- Rieger, Read Roberts, Guido van Rossum, Just van Rossum, Andreas Seidel,
- Georg Seifert, Miguel Sousa, Adam Twardoch, Adrien Tétar, Vitaly Volkov,
- Paul Wise.
-
- Copyrights
- ~~~~~~~~~~
-
- | Copyright (c) 1999-2004 Just van Rossum, LettError
- (just@letterror.com)
- | See `LICENSE <LICENSE>`__ for the full license.
-
- Copyright (c) 2000 BeOpen.com. All Rights Reserved.
-
- Copyright (c) 1995-2001 Corporation for National Research Initiatives.
- All Rights Reserved.
-
- Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam. All
- Rights Reserved.
-
- Have fun!
-
- .. |Travis Build Status| image:: https://travis-ci.org/fonttools/fonttools.svg
- :target: https://travis-ci.org/fonttools/fonttools
- .. |Appveyor Build status| image:: https://ci.appveyor.com/api/projects/status/0f7fmee9as744sl7/branch/master?svg=true
- :target: https://ci.appveyor.com/project/fonttools/fonttools/branch/master
- .. |Coverage Status| image:: https://codecov.io/gh/fonttools/fonttools/branch/master/graph/badge.svg
- :target: https://codecov.io/gh/fonttools/fonttools
- .. |PyPI| image:: https://img.shields.io/pypi/v/fonttools.svg
- :target: https://pypi.org/project/FontTools
- .. |Gitter Chat| image:: https://badges.gitter.im/fonttools-dev/Lobby.svg
- :alt: Join the chat at https://gitter.im/fonttools-dev/Lobby
- :target: https://gitter.im/fonttools-dev/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
-
- Changelog
- ~~~~~~~~~
-
- 3.44.0 (released 2019-08-02)
- ----------------------------
-
- - NOTE: This is the last scheduled release to support Python 2.7. The upcoming fonttools
- v4.x series is going to require Python 3.6 or greater.
- - [varLib] Added new ``varLib.instancer`` module for partially instantiating variable
- fonts. This extends (and will eventually replace) ``varLib.mutator`` module, as
- it allows to create not just full static instances from a variable font, but also
- "partial" or "less variable" fonts where some of the axes are dropped or
- instantiated at a particular value.
- Also available from the command-line as `fonttools varLib.instancer --help`
- (#1537, #1628).
- - [cffLib] Added support for ``FDSelect`` format 4 (#1677).
- - [subset] Added support for subsetting ``sbix`` (Apple bitmap color font) table.
- - [t1Lib] Fixed issue parsing ``eexec`` section in Type1 fonts when whitespace
- characters are interspersed among the trailing zeros (#1676).
- - [cffLib.specializer] Fixed bug in ``programToCommands`` with CFF2 charstrings (#1669).
-
- 3.43.2 (released 2019-07-10)
- ----------------------------
-
- - [featureVars] Fixed region-merging code on python3 (#1659).
- - [varLib.cff] Fixed merging of sparse PrivateDict items (#1653).
-
- 3.43.1 (released 2019-06-19)
- ----------------------------
-
- - [subset] Fixed regression when passing ``--flavor=woff2`` option with an input font
- that was already compressed as WOFF 1.0 (#1650).
-
- 3.43.0 (released 2019-06-18)
- ----------------------------
-
- - [woff2] Added support for compressing/decompressing WOFF2 fonts with non-transformed
- ``glyf`` and ``loca`` tables, as well as with transformed ``hmtx`` table.
- Removed ``Snippets/woff2_compress.py`` and ``Snippets/woff2_decompress.py`` scripts,
- and replaced them with a new console entry point ``fonttools ttLib.woff2``
- that provides two sub-commands ``compress`` and ``decompress``.
- - [varLib.cff] Fixed bug when merging CFF2 ``PrivateDicts``. The ``PrivateDict``
- data from the first region font was incorrecty used for all subsequent fonts.
- The bug would only affect variable CFF2 fonts with hinting (#1643, #1644).
- Also, fixed a merging bug when VF masters have no blends or marking glyphs (#1632,
- #1642).
- - [loggingTools] Removed unused backport of ``LastResortLogger`` class.
- - [subset] Gracefully handle partial MATH table (#1635).
- - [featureVars] Avoid duplicate references to ``rvrn`` feature record in
- ``DefaultLangSys`` tables when calling ``addFeatureVariations`` on a font that
- does not already have a ``GSUB`` table (aa8a5bc6).
- - [varLib] Fixed merging of class-based kerning. Before, the process could introduce
- rogue kerning values and variations for random classes against class zero (everything
- not otherwise classed).
- - [varLib] Fixed merging GPOS tables from master fonts with different number of
- ``SinglePos`` subtables (#1621, #1641).
- - [unicodedata] Updated Blocks, Scripts and ScriptExtensions to Unicode 12.1.
-
- 3.42.0 (released 2019-05-28)
- ----------------------------
-
- - [OS/2] Fixed sign of ``fsType``: it should be ``uint16``, not ``int16`` (#1619).
- - [subset] Skip out-of-range class values in mark attachment (#1478).
- - [fontBuilder] Add an empty ``DSIG`` table with ``setupDummyDSIG`` method (#1621).
- - [varLib.merger] Fixed bug whereby ``GDEF.GlyphClassDef`` were being dropped
- when generating instance via ``varLib.mutator`` (#1614).
- - [varLib] Added command-line options ``-v`` and ``-q`` to configure logging (#1613).
- - [subset] Update font extents in head table (#1612).
- - [subset] Make --retain-gids truncate empty glyphs after the last non-empty glyph
- (#1611).
- - [requirements] Updated ``unicodedata2`` backport for Unicode 12.0.
-
- 3.41.2 (released 2019-05-13)
- ----------------------------
-
- - [cffLib] Fixed issue when importing a ``CFF2`` variable font from XML, whereby
- the VarStore state was not propagated to PrivateDict (#1598).
- - [varLib] Don't drop ``post`` glyph names when building CFF2 variable font (#1609).
-
-
- 3.41.1 (released 2019-05-13)
- ----------------------------
-
- - [designspaceLib] Added ``loadSourceFonts`` method to load source fonts using
- custom opener function (#1606).
- - [head] Round font bounding box coordinates to integers to fix compile error
- if CFF font has float coordinates (#1604, #1605).
- - [feaLib] Don't write ``None`` in ``ast.ValueRecord.asFea()`` (#1599).
- - [subset] Fixed issue ``AssertionError`` when using ``--desubroutinize`` option
- (#1590, #1594).
- - [graphite] Fixed bug in ``Silf`` table's ``decompile`` method unmasked by
- previous typo fix (#1597). Decode languange code as UTF-8 in ``Sill`` table's
- ``decompile`` method (#1600).
-
- 3.41.0 (released 2019-04-29)
- ----------------------------
-
- - [varLib/cffLib] Added support for building ``CFF2`` variable font from sparse
- masters, or masters with more than one model (multiple ``VarStore.VarData``).
- In ``cffLib.specializer``, added support for ``CFF2`` CharStrings with
- ``blend`` operators (#1547, #1591).
- - [subset] Fixed subsetting ``HVAR`` and ``VVAR`` with ``--retain-gids`` option,
- and when advances mapping is null while sidebearings mappings are non-null
- (#1587, #1588).
- - Added ``otlLib.maxContextCalc`` module to compute ``OS/2.usMaxContext`` value.
- Calculate it automatically when compiling features with feaLib. Added option
- ``--recalc-max-context`` to ``subset`` module (#1582).
- - [otBase/otTables] Fixed ``AttributeError`` on missing OT table fields after
- importing font from TTX (#1584).
- - [graphite] Fixed typo ``Silf`` table's ``decompile`` method (#1586).
- - [otlLib] Better compress ``GPOS`` SinglePos (LookupType 1) subtables (#1539).
-
- 3.40.0 (released 2019-04-08)
- ----------------------------
-
- - [subset] Fixed error while subsetting ``VVAR`` with ``--retain-gids``
- option (#1552).
- - [designspaceLib] Use up-to-date default location in ``findDefault`` method
- (#1554).
- - [voltLib] Allow passing file-like object to Parser.
- - [arrayTools/glyf] ``calcIntBounds`` (used to compute bounding boxes of glyf
- table's glyphs) now uses ``otRound`` instead of ``round3`` (#1566).
- - [svgLib] Added support for converting more SVG shapes to path ``d`` strings
- (ellipse, line, polyline), as well as support for ``transform`` attributes.
- Only ``matrix`` transformations are currently supported (#1564, #1564).
- - [varLib] Added support for building ``VVAR`` table from ``vmtx`` and ``VORG``
- tables (#1551).
- - [fontBuilder] Enable making CFF2 fonts with ``post`` table format 2 (#1557).
- - Fixed ``DeprecationWarning`` on invalid escape sequences (#1562).
-
- 3.39.0 (released 2019-03-19)
- ----------------------------
-
- - [ttLib/glyf] Raise more specific error when encountering recursive
- component references (#1545, #1546).
- - [Doc/designspaceLib] Defined new ``public.skipExportGlyphs`` lib key (#1534,
- unified-font-object/ufo-spec#84).
- - [varLib] Use ``vmtx`` to compute vertical phantom points; or ``hhea.ascent``
- and ``head.unitsPerEM`` if ``vmtx`` is missing (#1528).
- - [gvar/cvar] Sort XML element's min/value/max attributes in TupleVariation
- toXML to improve readability of TTX dump (#1527).
- - [varLib.plot] Added support for 2D plots with only 1 variation axis (#1522).
- - [designspaceLib] Use axes maps when normalizing locations in
- DesignSpaceDocument (#1226, #1521), and when finding default source (#1535).
- - [mutator] Set ``OVERLAP_SIMPLE`` and ``OVERLAP_COMPOUND`` glyf flags by
- default in ``instantiateVariableFont``. Added ``--no-overlap`` cli option
- to disable this (#1518).
- - [subset] Fixed subsetting ``VVAR`` table (#1516, #1517).
- Fixed subsetting an ``HVAR`` table that has an ``AdvanceWidthMap`` when the
- option ``--retain-gids`` is used.
- - [feaLib] Added ``forceChained`` in MultipleSubstStatement (#1511).
- Fixed double indentation of ``subtable`` statement (#1512).
- Added support for ``subtable`` statement in more places than just PairPos
- lookups (#1520).
- Handle lookupflag 0 and lookupflag without a value (#1540).
- - [varLib] In ``load_designspace``, provide a default English name for the
- ``ital`` axis tag.
- - Remove pyftinspect because it is unmaintained and bitrotted.
-
- 3.38.0 (released 2019-02-18)
- ----------------------------
-
- - [cffLib] Fixed RecursionError when unpickling or deepcopying TTFont with
- CFF table (#1488, 649dc49).
- - [subset] Fixed AttributeError when using --desubroutinize option (#1490).
- Also, fixed desubroutinizing bug when subrs contain hints (#1499).
- - [CPAL] Make Color a subclass of namedtuple (173a0f5).
- - [feaLib] Allow hyphen in glyph class names.
- - [feaLib] Added 'tables' option to __main__.py (#1497).
- - [feaLib] Add support for special-case contextual positioning formatting
- (#1501).
- - [svgLib] Support converting SVG basic shapes (rect, circle, etc.) into
- equivalent SVG paths (#1500, #1508).
- - [Snippets] Added name-viewer.ipynb Jupyter notebook.
-
-
- 3.37.3 (released 2019-02-05)
- ----------------------------
-
- - The previous release accidentally changed several files from Unix to DOS
- line-endings. Fix that.
-
- 3.37.2 (released 2019-02-05)
- ----------------------------
-
- - [varLib] Temporarily revert the fix to ``load_masters()``, which caused a
- crash in ``interpolate_layout()`` when ``deepcopy``-ing OTFs.
-
- 3.37.1 (released 2019-02-05)
- ----------------------------
-
- - [varLib] ``load_masters()`` now actually assigns the fonts it loads to the
- source.font attributes.
- - [varLib] Fixed an MVAR table generation crash when sparse masters were
- involved.
- - [voltLib] ``parse_coverage_()`` returns a tuple instead of an ast.Enum.
- - [feaLib] A MarkClassDefinition inside a block is no longer doubly indented
- compared to the rest of the block.
-
- 3.37.0 (released 2019-01-28)
- ----------------------------
-
- - [svgLib] Added support for converting elliptical arcs to cubic bezier curves
- (#1464).
- - [py23] Added backport for ``math.isfinite``.
- - [varLib] Apply HIDDEN flag to fvar axis if designspace axis has attribute
- ``hidden=1``.
- - Fixed "DeprecationWarning: invalid escape sequence" in Python 3.7.
- - [voltLib] Fixed parsing glyph groups. Distinguish different PROCESS_MARKS.
- Accept COMPONENT glyph type.
- - [feaLib] Distinguish missing value and explicit ``<NULL>`` for PairPos2
- format A (#1459). Round-trip ``useExtension`` keyword. Implemented
- ``ValueRecord.asFea`` method.
- - [subset] Insert empty widths into hdmx when retaining gids (#1458).
-
- 3.36.0 (released 2019-01-17)
- ----------------------------
-
- - [ttx] Added ``--no-recalc-timestamp`` option to keep the original font's
- ``head.modified`` timestamp (#1455, #46).
- - [ttx/psCharStrings] Fixed issues while dumping and round-tripping CFF2 table
- with ttx (#1451, #1452, #1456).
- - [voltLib] Fixed check for duplicate anchors (#1450). Don't try to read past
- the ``END`` operator in .vtp file (#1453).
- - [varLib] Use sentinel value -0x8000 (-32768) to ignore post.underlineThickness
- and post.underlinePosition when generating MVAR deltas (#1449,
- googlei18n/ufo2ft#308).
- - [subset] Added ``--retain-gids`` option to subset font without modifying the
- current glyph indices (#1443, #1447).
- - [ufoLib] Replace deprecated calls to ``getbytes`` and ``setbytes`` with new
- equivalent ``readbytes`` and ``writebytes`` calls. ``fs`` >= 2.2 no required.
- - [varLib] Allow loading masters from TTX files as well (#1441).
-
- 3.35.2 (released 2019-01-14)
- ----------------------------
-
- - [hmtx/vmtx]: Allow to compile/decompile ``hmtx`` and ``vmtx`` tables even
- without the corresponding (required) metrics header tables, ``hhea`` and
- ``vhea`` (#1439).
- - [varLib] Added support for localized axes' ``labelname`` and named instances'
- ``stylename`` (#1438).
-
- 3.35.1 (released 2019-01-09)
- ----------------------------
-
- - [_m_a_x_p] Include ``maxComponentElements`` in ``maxp`` table's recalculation.
-
- 3.35.0 (released 2019-01-07)
- ----------------------------
-
- - [psCharStrings] In ``encodeFloat`` function, use float's "general format" with
- 8 digits of precision (i.e. ``%8g``) instead of ``str()``. This works around
- a macOS rendering issue when real numbers in CFF table are too long, and
- also makes sure that floats are encoded with the same precision in python 2.7
- and 3.x (#1430, googlei18n/ufo2ft#306).
- - [_n_a_m_e/fontBuilder] Make ``_n_a_m_e_table.addMultilingualName`` also add
- Macintosh (platformID=1) names by default. Added options to ``FontBuilder``
- ``setupNameTable`` method to optionally disable Macintosh or Windows names.
- (#1359, #1431).
- - [varLib] Make ``build`` optionally accept a ``DesignSpaceDocument`` object,
- instead of a designspace file path. The caller can now set the ``font``
- attribute of designspace's sources to a TTFont object, thus allowing to
- skip filenames manipulation altogether (#1416, #1425).
- - [sfnt] Allow SFNTReader objects to be deep-copied.
- - Require typing>=3.6.4 on py27 to fix issue with singledispatch (#1423).
- - [designspaceLib/t1Lib/macRes] Fixed some cases where pathlib.Path objects were
- not accepted (#1421).
- - [varLib] Fixed merging of multiple PairPosFormat2 subtables (#1411).
- - [varLib] The default STAT table version is now set to 1.1, to improve
- compatibility with legacy applications (#1413).
-
- 3.34.2 (released 2018-12-17)
- ----------------------------
-
- - [merge] Fixed AssertionError when none of the script tables in GPOS/GSUB have
- a DefaultLangSys record (#1408, 135a4a1).
-
- 3.34.1 (released 2018-12-17)
- ----------------------------
-
- - [varLib] Work around macOS rendering issue for composites without gvar entry (#1381).
-
- 3.34.0 (released 2018-12-14)
- ----------------------------
-
- - [varLib] Support generation of CFF2 variable fonts. ``model.reorderMasters()``
- now supports arbitrary mapping. Fix handling of overlapping ranges for feature
- variations (#1400).
- - [cffLib, subset] Code clean-up and fixing related to CFF2 support.
- - [ttLib.tables.ttProgram] Use raw strings for regex patterns (#1389).
- - [fontbuilder] Initial support for building CFF2 fonts. Set CFF's
- ``FontMatrix`` automatically from unitsPerEm.
- - [plistLib] Accept the more general ``collections.Mapping`` instead of the
- specific ``dict`` class to support custom data classes that should serialize
- to dictionaries.
-
- 3.33.0 (released 2018-11-30)
- ----------------------------
- - [subset] subsetter bug fix with variable fonts.
- - [varLib.featureVar] Improve FeatureVariations generation with many rules.
- - [varLib] Enable sparse masters when building variable fonts:
- https://github.com/fonttools/fonttools/pull/1368#issuecomment-437257368
- - [varLib.mutator] Add IDEF for GETVARIATION opcode, for handling hints in an
- instance.
- - [ttLib] Ignore the length of kern table subtable format 0
-
- 3.32.0 (released 2018-11-01)
- ----------------------------
-
- - [ufoLib] Make ``UFOWriter`` a subclass of ``UFOReader``, and use mixins
- for shared methods (#1344).
- - [featureVars] Fixed normalization error when a condition's minimum/maximum
- attributes are missing in designspace ``<rule>`` (#1366).
- - [setup.py] Added ``[plot]`` to extras, to optionally install ``matplotlib``,
- needed to use the ``fonTools.varLib.plot`` module.
- - [varLib] Take total bounding box into account when resolving model (7ee81c8).
- If multiple axes have the same range ratio, cut across both (62003f4).
- - [subset] Don't error if ``STAT`` has no ``AxisValue`` tables.
- - [fontBuilder] Added a new submodule which contains a ``FontBuilder`` wrapper
- class around ``TTFont`` that makes it easier to create a working TTF or OTF
- font from scratch with code. NOTE: the API is still experimental and may
- change in future versions.
-
- 3.31.0 (released 2018-10-21)
- ----------------------------
-
- - [ufoLib] Merged the `ufoLib <https://github.com/unified-font-objects/ufoLib>`__
- master branch into a new ``fontTools.ufoLib`` package (#1335, #1095).
- Moved ``ufoLib.pointPen`` module to ``fontTools.pens.pointPen``.
- Moved ``ufoLib.etree`` module to ``fontTools.misc.etree``.
- Moved ``ufoLib.plistlib`` module to ``fontTools.misc.plistlib``.
- To use the new ``fontTools.ufoLib`` module you need to install fonttools
- with the ``[ufo]`` extra, or you can manually install the required additional
- dependencies (cf. README.rst).
- - [morx] Support AAT action type to insert glyphs and clean up compilation
- of AAT action tables (4a1871f, 2011ccf).
- - [subset] The ``--no-hinting`` on a CFF font now also drops the optional
- hinting keys in Private dict: ``ForceBold``, ``LanguageGroup``, and
- ``ExpansionFactor`` (#1322).
- - [subset] Include nameIDs referenced by STAT table (#1327).
- - [loggingTools] Added ``msg=None`` argument to
- ``CapturingLogHandler.assertRegex`` (0245f2c).
- - [varLib.mutator] Implemented ``FeatureVariations`` instantiation (#1244).
- - [g_l_y_f] Added PointPen support to ``_TTGlyph`` objects (#1334).
-
- 3.30.0 (released 2018-09-18)
- ----------------------------
-
- - [feaLib] Skip building noop class PairPos subtables when Coverage is NULL
- (#1318).
- - [ttx] Expose the previously reserved bit flag ``OVERLAP_SIMPLE`` of
- glyf table's contour points in the TTX dump. This is used in some
- implementations to specify a non-zero fill with overlapping contours (#1316).
- - [ttLib] Added support for decompiling/compiling ``TS1C`` tables containing
- VTT sources for ``cvar`` variation table (#1310).
- - [varLib] Use ``fontTools.designspaceLib`` to read DesignSpaceDocument. The
- ``fontTools.varLib.designspace`` module is now deprecated and will be removed
- in future versions. The presence of an explicit ``axes`` element is now
- required in order to build a variable font (#1224, #1313).
- - [varLib] Implemented building GSUB FeatureVariations table from the ``rules``
- element of DesignSpace document (#1240, #713, #1314).
- - [subset] Added ``--no-layout-closure`` option to not expand the subset with
- the glyphs produced by OpenType layout features. Instead, OpenType features
- will be subset to only rules that are relevant to the otherwise-specified
- glyph set (#43, #1121).
-
- 3.29.1 (released 2018-09-10)
- ----------------------------
-
- - [feaLib] Fixed issue whereby lookups from DFLT/dflt were not included in the
- DFLT/non-dflt language systems (#1307).
- - [graphite] Fixed issue on big-endian architectures (e.g. ppc64) (#1311).
- - [subset] Added ``--layout-scripts`` option to add/exclude set of OpenType
- layout scripts that will be preserved. By default all scripts are retained
- (``'*'``) (#1303).
-
- 3.29.0 (released 2018-07-26)
- ----------------------------
-
- - [feaLib] In the OTL table builder, when the ``name`` table is excluded
- from the list of tables to be build, skip compiling ``featureNames`` blocks,
- as the records referenced in ``FeatureParams`` table don't exist (68951b7).
- - [otBase] Try ``ExtensionLookup`` if other offset-overflow methods fail
- (05f95f0).
- - [feaLib] Added support for explicit ``subtable;`` break statements in
- PairPos lookups; previously these were ignored (#1279, #1300, #1302).
- - [cffLib.specializer] Make sure the stack depth does not exceed maxstack - 1,
- so that a subroutinizer can insert subroutine calls (#1301,
- https://github.com/googlei18n/ufo2ft/issues/266).
- - [otTables] Added support for fixing offset overflow errors occurring inside
- ``MarkBasePos`` subtables (#1297).
- - [subset] Write the default output file extension based on ``--flavor`` option,
- or the value of ``TTFont.sfntVersion`` (d7ac0ad).
- - [unicodedata] Updated Blocks, Scripts and ScriptExtensions for Unicode 11
- (452c85e).
- - [xmlWriter] Added context manager to XMLWriter class to autoclose file
- descriptor on exit (#1290).
- - [psCharStrings] Optimize the charstring's bytecode by encoding as integers
- all float values that have no decimal portion (8d7774a).
- - [ttFont] Fixed missing import of ``TTLibError`` exception (#1285).
- - [feaLib] Allow any languages other than ``dflt`` under ``DFLT`` script
- (#1278, #1292).
-
- 3.28.0 (released 2018-06-19)
- ----------------------------
-
- - [featureVars] Added experimental module to build ``FeatureVariations``
- tables. Still needs to be hooked up to ``varLib.build`` (#1240).
- - [fixedTools] Added ``otRound`` to round floats to nearest integer towards
- positive Infinity. This is now used where we deal with visual data like X/Y
- coordinates, advance widths/heights, variation deltas, and similar (#1274,
- #1248).
- - [subset] Improved GSUB closure memoize algorithm.
- - [varLib.models] Fixed regression in model resolution (180124, #1269).
- - [feaLib.ast] Fixed error when converting ``SubtableStatement`` to string
- (#1275).
- - [varLib.mutator] Set ``OS/2.usWeightClass`` and ``usWidthClass``, and
- ``post.italicAngle`` based on the 'wght', 'wdth' and 'slnt' axis values
- (#1276, #1264).
- - [py23/loggingTools] Don't automatically set ``logging.lastResort`` handler
- on py27. Moved ``LastResortLogger`` to the ``loggingTools`` module (#1277).
-
- 3.27.1 (released 2018-06-11)
- ----------------------------
-
- - [ttGlyphPen] Issue a warning and skip building non-existing components
- (https://github.com/googlei18n/fontmake/issues/411).
- - [tests] Fixed issue running ttx_test.py from a tagged commit.
-
- 3.27.0 (released 2018-06-11)
- ----------------------------
-
- - [designspaceLib] Added new ``conditionSet`` element to ``rule`` element in
- designspace document. Bumped ``format`` attribute to ``4.0`` (previously,
- it was formatted as an integer). Removed ``checkDefault``, ``checkAxes``
- methods, and any kind of guessing about the axes when the ``<axes>`` element
- is missing. The default master is expected at the intersection of all default
- values for each axis (#1254, #1255, #1267).
- - [cffLib] Fixed issues when compiling CFF2 or converting from CFF when the
- font has an FDArray (#1211, #1271).
- - [varLib] Avoid attempting to build ``cvar`` table when ``glyf`` table is not
- present, as is the case for CFF2 fonts.
- - [subset] Handle None coverages in MarkGlyphSets; revert commit 02616ab that
- sets empty Coverage tables in MarkGlyphSets to None, to make OTS happy.
- - [ttFont] Allow to build glyph order from ``maxp.numGlyphs`` when ``post`` or
- ``cmap`` are missing.
- - [ttFont] Added ``__len__`` method to ``_TTGlyphSet``.
- - [glyf] Ensure ``GlyphCoordinates`` never overflow signed shorts (#1230).
- - [py23] Added alias for ``itertools.izip`` shadowing the built-in ``zip``.
- - [loggingTools] Memoize ``log`` property of ``LogMixin`` class (fbab12).
- - [ttx] Impoved test coverage (#1261).
- - [Snippets] Addded script to append a suffix to all family names in a font.
- - [varLib.plot] Make it work with matplotlib >= 2.1 (b38e2b).
-
- 3.26.0 (released 2018-05-03)
- ----------------------------
-
- - [designspace] Added a new optional ``layer`` attribute to the source element,
- and a corresponding ``layerName`` attribute to the ``SourceDescriptor``
- object (#1253).
- Added ``conditionset`` element to the ``rule`` element to the spec, but not
- implemented in designspace reader/writer yet (#1254).
- - [varLib.models] Refine modeling one last time (0ecf5c5).
- - [otBase] Fixed sharing of tables referred to by different offset sizes
- (795f2f9).
- - [subset] Don't drop a GDEF that only has VarStore (fc819d6). Set to None
- empty Coverage tables in MarkGlyphSets (02616ab).
- - [varLib]: Added ``--master-finder`` command-line option (#1249).
- - [varLib.mutator] Prune fvar nameIDs from instance's name table (#1245).
- - [otTables] Allow decompiling bad ClassDef tables with invalid format, with
- warning (#1236).
- - [varLib] Make STAT v1.2 and reuse nameIDs from fvar table (#1242).
- - [varLib.plot] Show master locations. Set axis limits to -1, +1.
- - [subset] Handle HVAR direct mapping. Passthrough 'cvar'.
- Added ``--font-number`` command-line option for collections.
- - [t1Lib] Allow a text encoding to be specified when parsing a Type 1 font
- (#1234). Added ``kind`` argument to T1Font constructor (c5c161c).
- - [ttLib] Added context manager API to ``TTFont`` class, so it can be used in
- ``with`` statements to auto-close the file when exiting the context (#1232).
-
- 3.25.0 (released 2018-04-03)
- ----------------------------
-
- - [varLib] Improved support-resolution algorithm. Previously, the on-axis
- masters would always cut the space. They don't anymore. That's more
- consistent, and fixes the main issue Erik showed at TYPO Labs 2017.
- Any varfont built that had an unusual master configuration will change
- when rebuilt (42bef17, a523a697,
- https://github.com/googlei18n/fontmake/issues/264).
- - [varLib.models] Added a ``main()`` entry point, that takes positions and
- prints model results.
- - [varLib.plot] Added new module to plot a designspace's
- VariationModel. Requires ``matplotlib``.
- - [varLib.mutator] Added -o option to specify output file path (2ef60fa).
- - [otTables] Fixed IndexError while pruning of HVAR pre-write (6b6c34a).
- - [varLib.models] Convert delta array to floats if values overflows signed
- short integer (0055f94).
-
- 3.24.2 (released 2018-03-26)
- ----------------------------
-
- - [otBase] Don't fail during ``ValueRecord`` copy if src has more items.
- We drop hinting in the subsetter by simply changing ValueFormat, without
- cleaning up the actual ValueRecords. This was causing assertion error if
- a variable font was subsetted without hinting and then passed directly to
- the mutator for instantiation without first it saving to disk.
-
- 3.24.1 (released 2018-03-06)
- ----------------------------
-
- - [varLib] Don't remap the same ``DeviceTable`` twice in VarStore optimizer
- (#1206).
- - [varLib] Add ``--disable-iup`` option to ``fonttools varLib`` script,
- and a ``optimize=True`` keyword argument to ``varLib.build`` function,
- to optionally disable IUP optimization while building varfonts.
- - [ttCollection] Fixed issue while decompiling ttc with python3 (#1207).
-
- 3.24.0 (released 2018-03-01)
- ----------------------------
-
- - [ttGlyphPen] Decompose composite glyphs if any components' transform is too
- large to fit a ``F2Dot14`` value, or clamp transform values that are
- (almost) equal to +2.0 to make them fit and avoid decomposing (#1200,
- #1204, #1205).
- - [ttx] Added new ``-g`` option to dump glyphs from the ``glyf`` table
- splitted as individual ttx files (#153, #1035, #1132, #1202).
- - Copied ``ufoLib.filenames`` module to ``fontTools.misc.filenames``, used
- for the ttx split-glyphs option (#1202).
- - [feaLib] Added support for ``cvParameters`` blocks in Character Variant
- feautures ``cv01-cv99`` (#860, #1169).
- - [Snippets] Added ``checksum.py`` script to generate/check SHA1 hash of
- ttx files (#1197).
- - [varLib.mutator] Fixed issue while instantiating some variable fonts
- whereby the horizontal advance width computed from ``gvar`` phantom points
- could turn up to be negative (#1198).
- - [varLib/subset] Fixed issue with subsetting GPOS variation data not
- picking up ``ValueRecord`` ``Device`` objects (54fd71f).
- - [feaLib/voltLib] In all AST elements, the ``location`` is no longer a
- required positional argument, but an optional kewyord argument (defaults
- to ``None``). This will make it easier to construct feature AST from
- code (#1201).
-
-
- 3.23.0 (released 2018-02-26)
- ----------------------------
-
- - [designspaceLib] Added an optional ``lib`` element to the designspace as a
- whole, as well as to the instance elements, to store arbitrary data in a
- property list dictionary, similar to the UFO's ``lib``. Added an optional
- ``font`` attribute to the ``SourceDescriptor``, to allow operating on
- in-memory font objects (#1175).
- - [cffLib] Fixed issue with lazy-loading of attributes when attempting to
- set the CFF TopDict.Encoding (#1177, #1187).
- - [ttx] Fixed regression introduced in 3.22.0 that affected the split tables
- ``-s`` option (#1188).
- - [feaLib] Added ``IncludedFeaNotFound`` custom exception subclass, raised
- when an included feature file cannot be found (#1186).
- - [otTables] Changed ``VarIdxMap`` to use glyph names internally instead of
- glyph indexes. The old ttx dumps of HVAR/VVAR tables that contain indexes
- can still be imported (21cbab8, 38a0ffb).
- - [varLib] Implemented VarStore optimizer (#1184).
- - [subset] Implemented pruning of GDEF VarStore, HVAR and MVAR (#1179).
- - [sfnt] Restore backward compatiblity with ``numFonts`` attribute of
- ``SFNTReader`` object (#1181).
- - [merge] Initial support for merging ``LangSysRecords`` (#1180).
- - [ttCollection] don't seek(0) when writing to possibly unseekable strems.
- - [subset] Keep all ``--name-IDs`` from 0 to 6 by default (#1170, #605, #114).
- - [cffLib] Added ``width`` module to calculate optimal CFF default and
- nominal glyph widths.
- - [varLib] Don’t fail if STAT already in the master fonts (#1166).
-
- 3.22.0 (released 2018-02-04)
- ----------------------------
-
- - [subset] Support subsetting ``endchar`` acting as ``seac``-like components
- in ``CFF`` (fixes #1162).
- - [feaLib] Allow to build from pre-parsed ``ast.FeatureFile`` object.
- Added ``tables`` argument to only build some tables instead of all (#1159,
- #1163).
- - [textTools] Replaced ``safeEval`` with ``ast.literal_eval`` (#1139).
- - [feaLib] Added option to the parser to not resolve ``include`` statements
- (#1154).
- - [ttLib] Added new ``ttCollection`` module to read/write TrueType and
- OpenType Collections. Exports a ``TTCollection`` class with a ``fonts``
- attribute containing a list of ``TTFont`` instances, the methods ``save``
- and ``saveXML``, plus some list-like methods. The ``importXML`` method is
- not implemented yet (#17).
- - [unicodeadata] Added ``ot_tag_to_script`` function that converts from
- OpenType script tag to Unicode script code.
- - Added new ``designspaceLib`` subpackage, originally from Erik Van Blokland's
- ``designSpaceDocument``: https://github.com/LettError/designSpaceDocument
- NOTE: this is not yet used internally by varLib, and the API may be subject
- to changes (#911, #1110, LettError/designSpaceDocument#28).
- - Added new FontTools icon images (8ee7c32).
- - [unicodedata] Added ``script_horizontal_direction`` function that returns
- either "LTR" or "RTL" given a unicode script code.
- - [otConverters] Don't write descriptive name string as XML comment if the
- NameID value is 0 (== NULL) (#1151, #1152).
- - [unicodedata] Add ``ot_tags_from_script`` function to get the list of
- OpenType script tags associated with unicode script code (#1150).
- - [feaLib] Don't error when "enumerated" kern pairs conflict with preceding
- single pairs; emit warning and chose the first value (#1147, #1148).
- - [loggingTools] In ``CapturingLogHandler.assertRegex`` method, match the
- fully formatted log message.
- - [sbix] Fixed TypeError when concatenating str and bytes (#1154).
- - [bezierTools] Implemented cusp support and removed ``approximate_fallback``
- arg in ``calcQuadraticArcLength``. Added ``calcCubicArcLength`` (#1142).
-
- 3.21.2 (released 2018-01-08)
- ----------------------------
-
- - [varLib] Fixed merging PairPos Format1/2 with missing subtables (#1125).
-
- 3.21.1 (released 2018-01-03)
- ----------------------------
-
- - [feaLib] Allow mixed single/multiple substitutions (#612)
- - Added missing ``*.afm`` test assets to MAINFEST.in (#1137).
- - Fixed dumping ``SVG`` tables containing color palettes (#1124).
-
- 3.21.0 (released 2017-12-18)
- ----------------------------
-
- - [cmap] when compiling format6 subtable, don't assume gid0 is always called
- '.notdef' (1e42224).
- - [ot] Allow decompiling fonts with bad Coverage format number (1aafae8).
- - Change FontTools licence to MIT (#1127).
- - [post] Prune extra names already in standard Mac set (df1e8c7).
- - [subset] Delete empty SubrsIndex after subsetting (#994, #1118).
- - [varLib] Don't share points in cvar by default, as it currently fails on
- some browsers (#1113).
- - [afmLib] Make poor old afmLib work on python3.
-
- 3.20.1 (released 2017-11-22)
- ----------------------------
-
- - [unicodedata] Fixed issue with ``script`` and ``script_extension`` functions
- returning inconsistent short vs long names. They both return the short four-
- letter script codes now. Added ``script_name`` and ``script_code`` functions
- to look up the long human-readable script name from the script code, and
- viceversa (#1109, #1111).
-
- 3.20.0 (released 2017-11-21)
- ----------------------------
-
- - [unicodedata] Addded new module ``fontTools.unicodedata`` which exports the
- same interface as the built-in ``unicodedata`` module, with the addition of
- a few functions that are missing from the latter, such as ``script``,
- ``script_extension`` and ``block``. Added a ``MetaTools/buildUCD.py`` script
- to download and parse data files from the Unicode Character Database and
- generate python modules containing lists of ranges and property values.
- - [feaLib] Added ``__str__`` method to all ``ast`` elements (delegates to the
- ``asFea`` method).
- - [feaLib] ``Parser`` constructor now accepts a ``glyphNames`` iterable
- instead of ``glyphMap`` dict. The latter still works but with a pending
- deprecation warning (#1104).
- - [bezierTools] Added arc length calculation functions originally from
- ``pens.perimeterPen`` module (#1101).
- - [varLib] Started generating STAT table (8af4309). Right now it just reflects
- the axes, and even that with certain limitations:
- * AxisOrdering is set to the order axes are defined,
- * Name-table entries are not shared with fvar.
- - [py23] Added backports for ``redirect_stdout`` and ``redirect_stderr``
- context managers (#1097).
- - [Graphite] Fixed some round-trip bugs (#1093).
-
- 3.19.0 (released 2017-11-06)
- ----------------------------
-
- - [varLib] Try set of used points instead of all points when testing whether to
- share points between tuples (#1090).
- - [CFF2] Fixed issue with reading/writing PrivateDict BlueValues to TTX file.
- Read the commit message 8b02b5a and issue #1030 for more details.
- NOTE: this change invalidates all the TTX files containing CFF2 tables
- that where dumped with previous verisons of fonttools.
- CFF2 Subr items can have values on the stack after the last operator, thus
- a ``CFF2Subr`` class was added to accommodate this (#1091).
- - [_k_e_r_n] Fixed compilation of AAT kern version=1.0 tables (#1089, #1094)
- - [ttLib] Added getBestCmap() convenience method to TTFont class and cmap table
- class that returns a preferred Unicode cmap subtable given a list of options
- (#1092).
- - [morx] Emit more meaningful subtable flags. Implement InsertionMorphAction
-
- 3.18.0 (released 2017-10-30)
- ----------------------------
-
- - [feaLib] Fixed writing back nested glyph classes (#1086).
- - [TupleVariation] Reactivated shared points logic, bugfixes (#1009).
- - [AAT] Implemented ``morx`` ligature subtables (#1082).
- - [reverseContourPen] Keep duplicate lineTo following a moveTo (#1080,
- https://github.com/googlei18n/cu2qu/issues/51).
- - [varLib.mutator] Suport instantiation of GPOS, GDEF and MVAR (#1079).
- - [sstruct] Fixed issue with ``unicode_literals`` and ``struct`` module in
- old versions of python 2.7 (#993).
-
- 3.17.0 (released 2017-10-16)
- ----------------------------
-
- - [svgPathPen] Added an ``SVGPathPen`` that translates segment pen commands
- into SVG path descriptions. Copied from Tal Leming's ``ufo2svg.svgPathPen``
- https://github.com/typesupply/ufo2svg/blob/d69f992/Lib/ufo2svg/svgPathPen.py
- - [reverseContourPen] Added ``ReverseContourPen``, a filter pen that draws
- contours with the winding direction reversed, while keeping the starting
- point (#1071).
- - [filterPen] Added ``ContourFilterPen`` to manipulate contours as a whole
- rather than segment by segment.
- - [arrayTools] Added ``Vector`` class to apply math operations on an array
- of numbers, and ``pairwise`` function to loop over pairs of items in an
- iterable.
- - [varLib] Added support for building and interpolation of ``cvar`` table
- (f874cf6, a25a401).
-
- 3.16.0 (released 2017-10-03)
- ----------------------------
-
- - [head] Try using ``SOURCE_DATE_EPOCH`` environment variable when setting
- the ``head`` modified timestamp to ensure reproducible builds (#1063).
- See https://reproducible-builds.org/specs/source-date-epoch/
- - [VTT] Decode VTT's ``TSI*`` tables text as UTF-8 (#1060).
- - Added support for Graphite font tables: Feat, Glat, Gloc, Silf and Sill.
- Thanks @mhosken! (#1054).
- - [varLib] Default to using axis "name" attribute if "labelname" element
- is missing (588f524).
- - [merge] Added support for merging Script records. Remove unused features
- and lookups after merge (d802580, 556508b).
- - Added ``fontTools.svgLib`` package. Includes a parser for SVG Paths that
- supports the Pen protocol (#1051). Also, added a snippet to convert SVG
- outlines to UFO GLIF (#1053).
- - [AAT] Added support for ``ankr``, ``bsln``, ``mort``, ``morx``, ``gcid``,
- and ``cidg``.
- - [subset] Implemented subsetting of ``prop``, ``opbd``, ``bsln``, ``lcar``.
-
- 3.15.1 (released 2017-08-18)
- ----------------------------
-
- - [otConverters] Implemented ``__add__`` and ``__radd__`` methods on
- ``otConverters._LazyList`` that decompile a lazy list before adding
- it to another list or ``_LazyList`` instance. Fixes an ``AttributeError``
- in the ``subset`` module when attempting to sum ``_LazyList`` objects
- (6ef48bd2, 1aef1683).
- - [AAT] Support the `opbd` table with optical bounds (a47f6588).
- - [AAT] Support `prop` table with glyph properties (d05617b4).
-
-
- 3.15.0 (released 2017-08-17)
- ----------------------------
-
- - [AAT] Added support for AAT lookups. The ``lcar`` table can be decompiled
- and recompiled; futher work needed to handle ``morx`` table (#1025).
- - [subset] Keep (empty) DefaultLangSys for Script 'DFLT' (6eb807b5).
- - [subset] Support GSUB/GPOS.FeatureVariations (fe01d87b).
- - [varLib] In ``models.supportScalars``, ignore an axis when its peak value
- is 0 (fixes #1020).
- - [varLib] Add default mappings to all axes in avar to fix rendering issue
- in some rasterizers (19c4b377, 04eacf13).
- - [varLib] Flatten multiple tail PairPosFormat2 subtables before merging
- (c55ef525).
- - [ttLib] Added support for recalculating font bounding box in ``CFF`` and
- ``head`` tables, and min/max values in ``hhea`` and ``vhea`` tables (#970).
-
- 3.14.0 (released 2017-07-31)
- ----------------------------
-
- - [varLib.merger] Remove Extensions subtables before merging (f7c20cf8).
- - [varLib] Initialize the avar segment map with required default entries
- (#1014).
- - [varLib] Implemented optimal IUP optmiziation (#1019).
- - [otData] Add ``AxisValueFormat4`` for STAT table v1.2 from OT v1.8.2
- (#1015).
- - [name] Fixed BCP46 language tag for Mac langID=9: 'si' -> 'sl'.
- - [subset] Return value from ``_DehintingT2Decompiler.op_hintmask``
- (c0d672ba).
- - [cffLib] Allow to get TopDict by index as well as by name (dca96c9c).
- - [cffLib] Removed global ``isCFF2`` state; use one set of classes for
- both CFF and CFF2, maintaining backward compatibility existing code (#1007).
- - [cffLib] Deprecated maxstack operator, per OpenType spec update 1.8.1.
- - [cffLib] Added missing default (-100) for UnderlinePosition (#983).
- - [feaLib] Enable setting nameIDs greater than 255 (#1003).
- - [varLib] Recalculate ValueFormat when merging SinglePos (#996).
- - [varLib] Do not emit MVAR if there are no entries in the variation store
- (#987).
- - [ttx] For ``-x`` option, pad with space if table tag length is < 4.
-
- 3.13.1 (released 2017-05-30)
- ----------------------------
-
- - [feaLib.builder] Removed duplicate lookups optimization. The original
- lookup order and semantics of the feature file are preserved (#976).
-
- 3.13.0 (released 2017-05-24)
- ----------------------------
-
- - [varLib.mutator] Implement IUP optimization (#969).
- - [_g_l_y_f.GlyphCoordinates] Changed ``__bool__()`` semantics to match those
- of other iterables (e46f949). Removed ``__abs__()`` (3db5be2).
- - [varLib.interpolate_layout] Added ``mapped`` keyword argument to
- ``interpolate_layout`` to allow disabling avar mapping: if False (default),
- the location is mapped using the map element of the axes in designspace file;
- if True, it is assumed that location is in designspace's internal space and
- no mapping is performed (#950, #975).
- - [varLib.interpolate_layout] Import designspace-loading logic from varLib.
- - [varLib] Fixed bug with recombining PairPosClass2 subtables (81498e5, #914).
- - [cffLib.specializer] When copying iterables, cast to list (462b7f86).
-
- 3.12.1 (released 2017-05-18)
- ----------------------------
-
- - [pens.t2CharStringPen] Fixed AttributeError when calling addComponent in
- T2CharStringPen (#965).
-
- 3.12.0 (released 2017-05-17)
- ----------------------------
-
- - [cffLib.specializer] Added new ``specializer`` module to optimize CFF
- charstrings, used by the T2CharStringPen (#948).
- - [varLib.mutator] Sort glyphs by component depth before calculating composite
- glyphs' bounding boxes to ensure deltas are correctly caclulated (#945).
- - [_g_l_y_f] Fixed loss of precision in GlyphCoordinates by using 'd' (double)
- instead of 'f' (float) as ``array.array`` typecode (#963, #964).
-
- 3.11.0 (released 2017-05-03)
- ----------------------------
-
- - [t2CharStringPen] Initial support for specialized Type2 path operators:
- vmoveto, hmoveto, vlineto, hlineto, vvcurveto, hhcurveto, vhcurveto and
- hvcurveto. This should produce more compact charstrings (#940, #403).
- - [Doc] Added Sphinx sources for the documentation. Thanks @gferreira (#935).
- - [fvar] Expose flags in XML (#932)
- - [name] Add helper function for building multi-lingual names (#921)
- - [varLib] Fixed kern merging when a PairPosFormat2 has ClassDef1 with glyphs
- that are NOT present in the Coverage (1b5e1c4, #939).
- - [varLib] Fixed non-deterministic ClassDef order with PY3 (f056c12, #927).
- - [feLib] Throw an error when the same glyph is defined in multiple mark
- classes within the same lookup (3e3ff00, #453).
-
- 3.10.0 (released 2017-04-14)
- ----------------------------
-
- - [varLib] Added support for building ``avar`` table, using the designspace
- ``<map>`` elements.
- - [varLib] Removed unused ``build(..., axisMap)`` argument. Axis map should
- be specified in designspace file now. We do not accept nonstandard axes
- if ``<axes>`` element is not present.
- - [varLib] Removed "custom" axis from the ``standard_axis_map``. This was
- added before when glyphsLib was always exporting the (unused) custom axis.
- - [varLib] Added partial support for building ``MVAR`` table; does not
- implement ``gasp`` table variations yet.
- - [pens] Added FilterPen base class, for pens that control another pen;
- factored out ``addComponent`` method from BasePen into a separate abstract
- DecomposingPen class; added DecomposingRecordingPen, which records
- components decomposed as regular contours.
- - [TSI1] Fixed computation of the textLength of VTT private tables (#913).
- - [loggingTools] Added ``LogMixin`` class providing a ``log`` property to
- subclasses, which returns a ``logging.Logger`` named after the latter.
- - [loggingTools] Added ``assertRegex`` method to ``CapturingLogHandler``.
- - [py23] Added backport for python 3's ``types.SimpleNamespace`` class.
- - [EBLC] Fixed issue with python 3 ``zip`` iterator.
-
- 3.9.2 (released 2017-04-08)
- ---------------------------
-
- - [pens] Added pen to draw glyphs using WxPython ``GraphicsPath`` class:
- https://wxpython.org/docs/api/wx.GraphicsPath-class.html
- - [varLib.merger] Fixed issue with recombining multiple PairPosFormat2
- subtables (#888)
- - [varLib] Do not encode gvar deltas that are all zeroes, or if all values
- are smaller than tolerance.
- - [ttLib] _TTGlyphSet glyphs now also have ``height`` and ``tsb`` (top
- side bearing) attributes from the ``vmtx`` table, if present.
- - [glyf] In ``GlyphCoordintes`` class, added ``__bool__`` / ``__nonzero__``
- methods, and ``array`` property to get raw array.
- - [ttx] Support reading TTX files with BOM (#896)
- - [CFF2] Fixed the reporting of the number of regions in the font.
-
- 3.9.1 (released 2017-03-20)
- ---------------------------
-
- - [varLib.merger] Fixed issue while recombining multiple PairPosFormat2
- subtables if they were split because of offset overflows (9798c30).
- - [varLib.merger] Only merge multiple PairPosFormat1 subtables if there is
- at least one of the fonts with a non-empty Format1 subtable (0f5a46b).
- - [varLib.merger] Fixed IndexError with empty ClassDef1 in PairPosFormat2
- (aad0d46).
- - [varLib.merger] Avoid reusing Class2Record (mutable) objects (e6125b3).
- - [varLib.merger] Calculate ClassDef1 and ClassDef2's Format when merging
- PairPosFormat2 (23511fd).
- - [macUtils] Added missing ttLib import (b05f203).
-
- 3.9.0 (released 2017-03-13)
- ---------------------------
-
- - [feaLib] Added (partial) support for parsing feature file comments ``# ...``
- appearing in between statements (#879).
- - [feaLib] Cleaned up syntax tree for FeatureNames.
- - [ttLib] Added support for reading/writing ``CFF2`` table (thanks to
- @readroberts at Adobe), and ``TTFA`` (ttfautohint) table.
- - [varLib] Fixed regression introduced with 3.8.0 in the calculation of
- ``NumShorts``, i.e. the number of deltas in ItemVariationData's delta sets
- that use a 16-bit representation (b2825ff).
-
- 3.8.0 (released 2017-03-05)
- ---------------------------
-
- - New pens: MomentsPen, StatisticsPen, RecordingPen, and TeePen.
- - [misc] Added new ``fontTools.misc.symfont`` module, for symbolic font
- statistical analysis; requires ``sympy`` (http://www.sympy.org/en/index.html)
- - [varLib] Added experimental ``fontTools.varLib.interpolatable`` module for
- finding wrong contour order between different masters
- - [varLib] designspace.load() now returns a dictionary, instead of a tuple,
- and supports <axes> element (#864); the 'masters' item was renamed 'sources',
- like the <sources> element in the designspace document
- - [ttLib] Fixed issue with recalculating ``head`` modified timestamp when
- saving CFF fonts
- - [ttLib] In TupleVariation, round deltas before compiling (#861, fixed #592)
- - [feaLib] Ignore duplicate glyphs in classes used as MarkFilteringSet and
- MarkAttachmentType (#863)
- - [merge] Changed the ``gasp`` table merge logic so that only the one from
- the first font is retained, similar to other hinting tables (#862)
- - [Tests] Added tests for the ``varLib`` package, as well as test fonts
- from the "Annotated OpenType Specification" (AOTS) to exercise ``ttLib``'s
- table readers/writers (<https://github.com/adobe-type-tools/aots>)
-
- 3.7.2 (released 2017-02-17)
- ---------------------------
-
- - [subset] Keep advance widths when stripping ".notdef" glyph outline in
- CID-keyed CFF fonts (#845)
- - [feaLib] Zero values now produce the same results as makeotf (#633, #848)
- - [feaLib] More compact encoding for “Contextual positioning with in-line
- single positioning rules” (#514)
-
- 3.7.1 (released 2017-02-15)
- ---------------------------
-
- - [subset] Fixed issue with ``--no-hinting`` option whereby advance widths in
- Type 2 charstrings were also being stripped (#709, #343)
- - [feaLib] include statements now resolve relative paths like makeotf (#838)
- - [feaLib] table ``name`` now handles Unicode codepoints beyond the Basic
- Multilingual Plane, also supports old-style MacOS platform encodings (#842)
- - [feaLib] correctly escape string literals when emitting feature syntax (#780)
-
- 3.7.0 (released 2017-02-11)
- ---------------------------
-
- - [ttx, mtiLib] Preserve ordering of glyph alternates in GSUB type 3 (#833).
- - [feaLib] Glyph names can have dashes, as per new AFDKO syntax v1.20 (#559).
- - [feaLib] feaLib.Parser now needs the font's glyph map for parsing.
- - [varLib] Fix regression where GPOS values were stored as 0.
- - [varLib] Allow merging of class-based kerning when ClassDefs are different
-
- 3.6.3 (released 2017-02-06)
- ---------------------------
-
- - [varLib] Fix building variation of PairPosFormat2 (b5c34ce).
- - Populate defaults even for otTables that have postRead (e45297b).
- - Fix compiling of MultipleSubstFormat1 with zero 'out' glyphs (b887860).
-
- 3.6.2 (released 2017-01-30)
- ---------------------------
-
- - [varLib.merger] Fixed "TypeError: reduce() of empty sequence with no
- initial value" (3717dc6).
-
- 3.6.1 (released 2017-01-28)
- ---------------------------
-
- - [py23] Fixed unhandled exception occurring at interpreter shutdown in
- the "last resort" logging handler (972b3e6).
- - [agl] Ensure all glyph names are of native 'str' type; avoid mixing
- 'str' and 'unicode' in TTFont.glyphOrder (d8c4058).
- - Fixed inconsistent title levels in README.rst that caused PyPI to
- incorrectly render the reStructuredText page.
-
- 3.6.0 (released 2017-01-26)
- ---------------------------
-
- - [varLib] Refactored and improved the variation-font-building process.
- - Assembly code in the fpgm, prep, and glyf tables is now indented in
- XML output for improved readability. The ``instruction`` element is
- written as a simple tag if empty (#819).
- - [ttx] Fixed 'I/O operation on closed file' error when dumping
- multiple TTXs to standard output with the '-o -' option.
- - The unit test modules (``*_test.py``) have been moved outside of the
- fontTools package to the Tests folder, thus they are no longer
- installed (#811).
-
- 3.5.0 (released 2017-01-14)
- ---------------------------
-
- - Font tables read from XML can now be written back to XML with no
- loss.
- - GSUB/GPOS LookupType is written out in XML as an element, not
- comment. (#792)
- - When parsing cmap table, do not store items mapped to glyph id 0.
- (#790)
- - [otlLib] Make ClassDef sorting deterministic. Fixes #766 (7d1ddb2)
- - [mtiLib] Added unit tests (#787)
- - [cvar] Implemented cvar table
- - [gvar] Renamed GlyphVariation to TupleVariation to match OpenType
- terminology.
- - [otTables] Handle gracefully empty VarData.Item array when compiling
- XML. (#797)
- - [varLib] Re-enabled generation of ``HVAR`` table for fonts with
- TrueType outlines; removed ``--build-HVAR`` command-line option.
- - [feaLib] The parser can now be extended to support non-standard
- statements in FEA code by using a customized Abstract Syntax Tree.
- See, for example, ``feaLib.builder_test.test_extensions`` and
- baseClass.feax (#794, fixes #773).
- - [feaLib] Added ``feaLib`` command to the 'fonttools' command-line
- tool; applies a feature file to a font. ``fonttools feaLib -h`` for
- help.
- - [pens] The ``T2CharStringPen`` now takes an optional
- ``roundTolerance`` argument to control the rounding of coordinates
- (#804, fixes #769).
- - [ci] Measure test coverage on all supported python versions and OSes,
- combine coverage data and upload to
- https://codecov.io/gh/fonttools/fonttools (#786)
- - [ci] Configured Travis and Appveyor for running tests on Python 3.6
- (#785, 55c03bc)
- - The manual pages installation directory can be customized through
- ``FONTTOOLS_MANPATH`` environment variable (#799, fixes #84).
- - [Snippets] Added otf2ttf.py, for converting fonts from CFF to
- TrueType using the googlei18n/cu2qu module (#802)
-
- 3.4.0 (released 2016-12-21)
- ---------------------------
-
- - [feaLib] Added support for generating FEA text from abstract syntax
- tree (AST) objects (#776). Thanks @mhosken
- - Added ``agl.toUnicode`` function to convert AGL-compliant glyph names
- to Unicode strings (#774)
- - Implemented MVAR table (b4d5381)
-
- 3.3.1 (released 2016-12-15)
- ---------------------------
-
- - [setup] We no longer use versioneer.py to compute fonttools version
- from git metadata, as this has caused issues for some users (#767).
- Now we bump the version strings manually with a custom ``release``
- command of setup.py script.
-
- 3.3.0 (released 2016-12-06)
- ---------------------------
-
- - [ttLib] Implemented STAT table from OpenType 1.8 (#758)
- - [cffLib] Fixed decompilation of CFF fonts containing non-standard
- key/value pairs in FontDict (issue #740; PR #744)
- - [py23] minor: in ``round3`` function, allow the second argument to be
- ``None`` (#757)
- - The standalone ``sstruct`` and ``xmlWriter`` modules, deprecated
- since vesion 3.2.0, have been removed. They can be imported from the
- ``fontTools.misc`` package.
-
- 3.2.3 (released 2016-12-02)
- ---------------------------
-
- - [py23] optimized performance of round3 function; added backport for
- py35 math.isclose() (9d8dacb)
- - [subset] fixed issue with 'narrow' (UCS-2) Python 2 builds and
- ``--text``/``--text-file`` options containing non-BMP chararcters
- (16d0e5e)
- - [varLib] fixed issuewhen normalizing location values (8fa2ee1, #749)
- - [inspect] Made it compatible with both python2 and python3 (167ee60,
- #748). Thanks @pnemade
-
- 3.2.2 (released 2016-11-24)
- ---------------------------
-
- - [varLib] Do not emit null axes in fvar (1bebcec). Thanks @robmck-ms
- - [varLib] Handle fonts without GPOS (7915a45)
- - [merge] Ignore LangSys if None (a11bc56)
- - [subset] Fix subsetting MathVariants (78d3cbe)
- - [OS/2] Fix "Private Use (plane 15)" range (08a0d55). Thanks @mashabow
-
- 3.2.1 (released 2016-11-03)
- ---------------------------
-
- - [OS/2] fix checking ``fsSelection`` bits matching ``head.macStyle``
- bits
- - [varLib] added ``--build-HVAR`` option to generate ``HVAR`` table for
- fonts with TrueType outlines. For ``CFF2``, it is enabled by default.
-
- 3.2.0 (released 2016-11-02)
- ---------------------------
-
- - [varLib] Improve support for OpenType 1.8 Variable Fonts:
- - Implement GDEF's VariationStore
- - Implement HVAR/VVAR tables
- - Partial support for loading MutatorMath .designspace files with
- varLib.designspace module
- - Add varLib.models with Variation fonts interpolation models
- - Implement GSUB/GPOS FeatureVariations
- - Initial support for interpolating and merging OpenType Layout tables
- (see ``varLib.interpolate_layout`` and ``varLib.merger`` modules)
- - [API change] Change version to be an integer instead of a float in
- XML output for GSUB, GPOS, GDEF, MATH, BASE, JSTF, HVAR, VVAR, feat,
- hhea and vhea tables. Scripts that set the Version for those to 1.0
- or other float values also need fixing. A warning is emitted when
- code or XML needs fix.
- - several bug fixes to the cffLib module, contributed by Adobe's
- @readroberts
- - The XML output for CFF table now has a 'major' and 'minor' elements
- for specifying whether it's version 1.0 or 2.0 (support for CFF2 is
- coming soon)
- - [setup.py] remove undocumented/deprecated ``extra_path`` Distutils
- argument. This means that we no longer create a "FontTools" subfolder
- in site-packages containing the actual fontTools package, as well as
- the standalone xmlWriter and sstruct modules. The latter modules are
- also deprecated, and scheduled for removal in upcoming releases.
- Please change your import statements to point to from fontTools.misc
- import xmlWriter and from fontTools.misc import sstruct.
- - [scripts] Add a 'fonttools' command-line tool that simply runs
- ``fontTools.*`` sub-modules: e.g. ``fonttools ttx``,
- ``fonttools subset``, etc.
- - [hmtx/vmts] Read advance width/heights as unsigned short (uint16);
- automatically round float values to integers.
- - [ttLib/xmlWriter] add 'newlinestr=None' keyword argument to
- ``TTFont.saveXML`` for overriding os-specific line endings (passed on
- to ``XMLWriter`` instances).
- - [versioning] Use versioneer instead of ``setuptools_scm`` to
- dynamically load version info from a git checkout at import time.
- - [feaLib] Support backslash-prefixed glyph names.
-
- 3.1.2 (released 2016-09-27)
- ---------------------------
-
- - restore Makefile as an alternative way to build/check/install
- - README.md: update instructions for installing package from source,
- and for running test suite
- - NEWS: Change log was out of sync with tagged release
-
- 3.1.1 (released 2016-09-27)
- ---------------------------
-
- - Fix ``ttLibVersion`` attribute in TTX files still showing '3.0'
- instead of '3.1'.
- - Use ``setuptools_scm`` to manage package versions.
-
- 3.1.0 (released 2016-09-26)
- ---------------------------
-
- - [feaLib] New library to parse and compile Adobe FDK OpenType Feature
- files.
- - [mtiLib] New library to parse and compile Monotype 'FontDame'
- OpenType Layout Tables files.
- - [voltLib] New library to parse Microsoft VOLT project files.
- - [otlLib] New library to work with OpenType Layout tables.
- - [varLib] New library to work with OpenType Font Variations.
- - [pens] Add ttGlyphPen to draw to TrueType glyphs, and t2CharStringPen
- to draw to Type 2 Charstrings (CFF); add areaPen and perimeterPen.
- - [ttLib.tables] Implement 'meta' and 'trak' tables.
- - [ttx] Add --flavor option for compiling to 'woff' or 'woff2'; add
- ``--with-zopfli`` option to use Zopfli to compress WOFF 1.0 fonts.
- - [subset] Support subsetting 'COLR'/'CPAL' and 'CBDT'/'CBLC' color
- fonts tables, and 'gvar' table for variation fonts.
- - [Snippets] Add ``symfont.py``, for symbolic font statistics analysis;
- interpolatable.py, a preliminary script for detecting interpolation
- errors; ``{merge,dump}_woff_metadata.py``.
- - [classifyTools] Helpers to classify things into classes.
- - [CI] Run tests on Windows, Linux and macOS using Appveyor and Travis
- CI; check unit test coverage with Coverage.py/Coveralls; automatic
- deployment to PyPI on tags.
- - [loggingTools] Use Python built-in logging module to print messages.
- - [py23] Make round() behave like Python 3 built-in round(); define
- round2() and round3().
-
- 3.0 (released 2015-09-01)
- -------------------------
-
- - Add Snippet scripts for cmap subtable format conversion, printing
- GSUB/GPOS features, building a GX font from two masters
- - TTX WOFF2 support and a ``-f`` option to overwrite output file(s)
- - Support GX tables: ``avar``, ``gvar``, ``fvar``, ``meta``
- - Support ``feat`` and gzip-compressed SVG tables
- - Upgrade Mac East Asian encodings to native implementation if
- available
- - Add Roman Croatian and Romanian encodings, codecs for mac-extended
- East Asian encodings
- - Implement optimal GLYF glyph outline packing; disabled by default
-
- 2.5 (released 2014-09-24)
- -------------------------
-
- - Add a Qt pen
- - Add VDMX table converter
- - Load all OpenType sub-structures lazily
- - Add support for cmap format 13.
- - Add pyftmerge tool
- - Update to Unicode 6.3.0d3
- - Add pyftinspect tool
- - Add support for Google CBLC/CBDT color bitmaps, standard EBLC/EBDT
- embedded bitmaps, and ``SVG`` table (thanks to Read Roberts at Adobe)
- - Add support for loading, saving and ttx'ing WOFF file format
- - Add support for Microsoft COLR/CPAL layered color glyphs
- - Support PyPy
- - Support Jython, by replacing numpy with array/lists modules and
- removed it, pure-Python StringIO, not cStringIO
- - Add pyftsubset and Subsetter object, supporting CFF and TTF
- - Add to ttx args for -q for quiet mode, -z to choose a bitmap dump
- format
-
- 2.4 (released 2013-06-22)
- -------------------------
-
- - Option to write to arbitrary files
- - Better dump format for DSIG
- - Better detection of OTF XML
- - Fix issue with Apple's kern table format
- - Fix mangling of TT glyph programs
- - Fix issues related to mona.ttf
- - Fix Windows Installer instructions
- - Fix some modern MacOS issues
- - Fix minor issues and typos
-
- 2.3 (released 2009-11-08)
- -------------------------
-
- - TrueType Collection (TTC) support
- - Python 2.6 support
- - Update Unicode data to 5.2.0
- - Couple of bug fixes
-
- 2.2 (released 2008-05-18)
- -------------------------
-
- - ClearType support
- - cmap format 1 support
- - PFA font support
- - Switched from Numeric to numpy
- - Update Unicode data to 5.1.0
- - Update AGLFN data to 1.6
- - Many bug fixes
-
- 2.1 (released 2008-01-28)
- -------------------------
-
- - Many years worth of fixes and features
-
- 2.0b2 (released 2002-??-??)
- ---------------------------
-
- - Be "forgiving" when interpreting the maxp table version field:
- interpret any value as 1.0 if it's not 0.5. Fixes dumping of these
- GPL fonts: http://www.freebsd.org/cgi/pds.cgi?ports/chinese/wangttf
- - Fixed ttx -l: it turned out this part of the code didn't work with
- Python 2.2.1 and earlier. My bad to do most of my testing with a
- different version than I shipped TTX with :-(
- - Fixed bug in ClassDef format 1 subtable (Andreas Seidel bumped into
- this one).
-
- 2.0b1 (released 2002-09-10)
- ---------------------------
-
- - Fixed embarrassing bug: the master checksum in the head table is now
- calculated correctly even on little-endian platforms (such as Intel).
- - Made the cmap format 4 compiler smarter: the binary data it creates
- is now more or less as compact as possible. TTX now makes more
- compact data than in any shipping font I've tested it with.
- - Dump glyph names as a separate "GlyphOrder" pseudo table as opposed
- to as part of the glyf table (obviously needed for CFF-OTF's).
- - Added proper support for the CFF table.
- - Don't barf on empty tables (questionable, but "there are font out
- there...")
- - When writing TT glyf data, align glyphs on 4-byte boundaries. This
- seems to be the current recommendation by MS. Also: don't barf on
- fonts which are already 4-byte aligned.
- - Windows installer contributed bu Adam Twardoch! Yay!
- - Changed the command line interface again, now by creating one new
- tool replacing the old ones: ttx It dumps and compiles, depending on
- input file types. The options have changed somewhat.
- - The -d option is back (output dir)
- - ttcompile's -i options is now called -m (as in "merge"), to avoid
- clash with dump's -i.
- - The -s option ("split tables") no longer creates a directory, but
- instead outputs a small .ttx file containing references to the
- individual table files. This is not a true link, it's a simple file
- name, and the referenced file should be in the same directory so
- ttcompile can find them.
- - compile no longer accepts a directory as input argument. Instead it
- can parse the new "mini-ttx" format as output by "ttx -s".
- - all arguments are input files
- - Renamed the command line programs and moved them to the Tools
- subdirectory. They are now installed by the setup.py install script.
- - Added OpenType support. BASE, GDEF, GPOS, GSUB and JSTF are (almost)
- fully supported. The XML output is not yet final, as I'm still
- considering to output certain subtables in a more human-friendly
- manner.
- - Fixed 'kern' table to correctly accept subtables it doesn't know
- about, as well as interpreting Apple's definition of the 'kern' table
- headers correctly.
- - Fixed bug where glyphnames were not calculated from 'cmap' if it was
- (one of the) first tables to be decompiled. More specifically: it
- cmap was the first to ask for a glyphID -> glyphName mapping.
- - Switched XML parsers: use expat instead of xmlproc. Should be faster.
- - Removed my UnicodeString object: I now require Python 2.0 or up,
- which has unicode support built in.
- - Removed assert in glyf table: redundant data at the end of the table
- is now ignored instead of raising an error. Should become a warning.
- - Fixed bug in hmtx/vmtx code that only occured if all advances were
- equal.
- - Fixed subtle bug in TT instruction disassembler.
- - Couple of fixes to the 'post' table.
- - Updated OS/2 table to latest spec.
-
- 1.0b1 (released 2001-08-10)
- ---------------------------
-
- - Reorganized the command line interface for ttDump.py and
- ttCompile.py, they now behave more like "normal" command line tool,
- in that they accept multiple input files for batch processing.
- - ttDump.py and ttCompile.py don't silently override files anymore, but
- ask before doing so. Can be overridden by -f.
- - Added -d option to both ttDump.py and ttCompile.py.
- - Installation is now done with distutils. (Needs work for environments
- without compilers.)
- - Updated installation instructions.
- - Added some workarounds so as to handle certain buggy fonts more
- gracefully.
- - Updated Unicode table to Unicode 3.0 (Thanks Antoine!)
- - Included a Python script by Adam Twardoch that adds some useful stuff
- to the Windows registry.
- - Moved the project to SourceForge.
-
- 1.0a6 (released 2000-03-15)
- ---------------------------
-
- - Big reorganization: made ttLib a subpackage of the new fontTools
- package, changed several module names. Called the entire suite
- "FontTools"
- - Added several submodules to fontTools, some new, some older.
- - Added experimental CFF/GPOS/GSUB support to ttLib, read-only (but XML
- dumping of GPOS/GSUB is for now disabled)
- - Fixed hdmx endian bug
- - Added -b option to ttCompile.py, it disables recalculation of
- bounding boxes, as requested by Werner Lemberg.
- - Renamed tt2xml.pt to ttDump.py and xml2tt.py to ttCompile.py
- - Use ".ttx" as file extension instead of ".xml".
- - TTX is now the name of the XML-based *format* for TT fonts, and not
- just an application.
-
- 1.0a5
- -----
-
- Never released
-
- - More tables supported: hdmx, vhea, vmtx
-
- 1.0a3 & 1.0a4
- -------------
-
- Never released
-
- - fixed most portability issues
- - retracted the "Euro_or_currency" change from 1.0a2: it was
- nonsense!
-
- 1.0a2 (released 1999-05-02)
- ---------------------------
-
- - binary release for MacOS
- - genenates full FOND resources: including width table, PS font name
- info and kern table if applicable.
- - added cmap format 4 support. Extra: dumps Unicode char names as XML
- comments!
- - added cmap format 6 support
- - now accepts true type files starting with "true" (instead of just
- 0x00010000 and "OTTO")
- - 'glyf' table support is now complete: I added support for composite
- scale, xy-scale and two-by-two for the 'glyf' table. For now,
- component offset scale behaviour defaults to Apple-style. This only
- affects the (re)calculation of the glyph bounding box.
- - changed "Euro" to "Euro_or_currency" in the Standard Apple Glyph
- order list, since we cannot tell from the 'post' table which is
- meant. I should probably doublecheck with a Unicode encoding if
- available. (This does not affect the output!)
-
- Fixed bugs: - 'hhea' table is now recalculated correctly - fixed wrong
- assumption about sfnt resource names
-
- 1.0a1 (released 1999-04-27)
- ---------------------------
-
- - initial binary release for MacOS
-
-Platform: Any
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Environment :: Console
-Classifier: Environment :: Other Environment
-Classifier: Intended Audience :: Developers
-Classifier: Intended Audience :: End Users/Desktop
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Natural Language :: English
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 3
-Classifier: Topic :: Text Processing :: Fonts
-Classifier: Topic :: Multimedia :: Graphics
-Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
-Provides-Extra: woff
-Provides-Extra: all
-Provides-Extra: symfont
-Provides-Extra: interpolatable
-Provides-Extra: unicode
-Provides-Extra: type1
-Provides-Extra: lxml
-Provides-Extra: ufo
-Provides-Extra: plot
-Provides-Extra: graphite
diff --git a/README.rst b/README.rst
index cd7c02af..97d23e4b 100644
--- a/README.rst
+++ b/README.rst
@@ -1,4 +1,4 @@
-|Travis Build Status| |Appveyor Build status| |Coverage Status| |PyPI| |Gitter Chat|
+|CI Build Status| |Coverage Status| |PyPI| |Gitter Chat|
What is this?
~~~~~~~~~~~~~
@@ -11,16 +11,19 @@ What is this?
licence <LICENSE>`__.
| Among other things this means you can use it free of charge.
+`User documentation <https://fonttools.readthedocs.io/en/latest/>`_ and
+`developer documentation <https://fonttools.readthedocs.io/en/latest/developer.html>`_
+are available at `Read the Docs <https://fonttools.readthedocs.io/>`_.
+
Installation
~~~~~~~~~~~~
-FontTools requires `Python <http://www.python.org/download/>`__ 2.7, 3.4
-or later.
+FontTools 4.x requires `Python <http://www.python.org/download/>`__ 3.6
+or later. FontTools 3.x requires Python 2.7 or later.
**NOTE** From August 2019, until no later than January 1 2020, the support
for *Python 2.7* will be limited to only critical bug fixes, and no new features
-will be added to the ``py27`` branch. The upcoming FontTools 4.x series will require
-*Python 3.6* or above. You can read more `here <https://python3statement.org>`__
+will be added to the ``py27`` branch. You can read more `here <https://python3statement.org>`__
and `here <https://github.com/fonttools/fonttools/issues/765>`__ for the
reasons behind this decision.
@@ -55,112 +58,6 @@ Python 3 `venv <https://docs.python.org/3/library/venv.html>`__ module.
# install in 'editable' mode
pip install -e .
-TTX – From OpenType and TrueType to XML and Back
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Once installed you can use the ``ttx`` command to convert binary font
-files (``.otf``, ``.ttf``, etc) to the TTX XML format, edit them, and
-convert them back to binary format. TTX files have a .ttx file
-extension.
-
-.. code:: sh
-
- ttx /path/to/font.otf
- ttx /path/to/font.ttx
-
-The TTX application can be used in two ways, depending on what
-platform you run it on:
-
-- As a command line tool (Windows/DOS, Unix, macOS)
-- By dropping files onto the application (Windows, macOS)
-
-TTX detects what kind of files it is fed: it will output a ``.ttx`` file
-when it sees a ``.ttf`` or ``.otf``, and it will compile a ``.ttf`` or
-``.otf`` when the input file is a ``.ttx`` file. By default, the output
-file is created in the same folder as the input file, and will have the
-same name as the input file but with a different extension. TTX will
-*never* overwrite existing files, but if necessary will append a unique
-number to the output filename (before the extension) such as
-``Arial#1.ttf``
-
-When using TTX from the command line there are a bunch of extra options.
-These are explained in the help text, as displayed when typing
-``ttx -h`` at the command prompt. These additional options include:
-
-- specifying the folder where the output files are created
-- specifying which tables to dump or which tables to exclude
-- merging partial ``.ttx`` files with existing ``.ttf`` or ``.otf``
- files
-- listing brief table info instead of dumping to ``.ttx``
-- splitting tables to separate ``.ttx`` files
-- disabling TrueType instruction disassembly
-
-The TTX file format
--------------------
-
-The following tables are currently supported:
-
-.. begin table list
-.. code::
-
- BASE, CBDT, CBLC, CFF, CFF2, COLR, CPAL, DSIG, EBDT, EBLC, FFTM,
- Feat, GDEF, GMAP, GPKG, GPOS, GSUB, Glat, Gloc, HVAR, JSTF, LTSH,
- MATH, META, MVAR, OS/2, SING, STAT, SVG, Silf, Sill, TSI0, TSI1,
- TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS, TSIV, TTFA, VDMX,
- VORG, VVAR, ankr, avar, bsln, cidg, cmap, cvar, cvt, feat, fpgm,
- fvar, gasp, gcid, glyf, gvar, hdmx, head, hhea, hmtx, kern, lcar,
- loca, ltag, maxp, meta, mort, morx, name, opbd, post, prep, prop,
- sbix, trak, vhea and vmtx
-.. end table list
-
-Other tables are dumped as hexadecimal data.
-
-TrueType fonts use glyph indices (GlyphIDs) to refer to glyphs in most
-places. While this is fine in binary form, it is really hard to work
-with for humans. Therefore we use names instead.
-
-The glyph names are either extracted from the ``CFF`` table or the
-``post`` table, or are derived from a Unicode ``cmap`` table. In the
-latter case the Adobe Glyph List is used to calculate names based on
-Unicode values. If all of these methods fail, names are invented based
-on GlyphID (eg ``glyph00142``)
-
-It is possible that different glyphs use the same name. If this happens,
-we force the names to be unique by appending ``#n`` to the name (``n``
-being an integer number.) The original names are being kept, so this has
-no influence on a "round tripped" font.
-
-Because the order in which glyphs are stored inside the binary font is
-important, we maintain an ordered list of glyph names in the font.
-
-Other Tools
-~~~~~~~~~~~
-
-Commands for merging and subsetting fonts are also available:
-
-.. code:: sh
-
- pyftmerge
- pyftsubset
-
-fontTools Python Module
-~~~~~~~~~~~~~~~~~~~~~~~
-
-The fontTools Python module provides a convenient way to
-programmatically edit font files.
-
-.. code:: py
-
- >>> from fontTools.ttLib import TTFont
- >>> font = TTFont('/path/to/font.ttf')
- >>> font
- <fontTools.ttLib.TTFont object at 0x10c34ed50>
- >>>
-
-A selection of sample Python programs is in the
-`Snippets <https://github.com/fonttools/fonttools/blob/master/Snippets/>`__
-directory.
-
Optional Requirements
---------------------
@@ -184,7 +81,7 @@ are required to unlock the extra features named "ufo", etc.
The module exports a ElementTree-like API for reading/writing XML files, and
allows to use as the backend either the built-in ``xml.etree`` module or
- `lxml <https://http://lxml.de>`__. The latter is preferred whenever present,
+ `lxml <https://lxml.de>`__. The latter is preferred whenever present,
as it is generally faster and more secure.
*Extra:* ``lxml``
@@ -277,9 +174,19 @@ are required to unlock the extra features named "ufo", etc.
*Extra:* ``type1``
-- ``Lib/fontTools/pens/cocoaPen.py``
+- ``Lib/fontTools/ttLib/removeOverlaps.py``
+
+ Simplify TrueType glyphs by merging overlapping contours and components.
+
+ * `skia-pathops <https://pypi.python.org/pypy/skia-pathops>`__: Python
+ bindings for the Skia library's PathOps module, performing boolean
+ operations on paths (union, intersection, etc.).
+
+ *Extra:* ``pathops``
- Pen for drawing glyphs with Cocoa ``NSBezierPath``, requires:
+- ``Lib/fontTools/pens/cocoaPen.py`` and ``Lib/fontTools/pens/quartzPen.py``
+
+ Pens for drawing glyphs with Cocoa ``NSBezierPath`` or ``CGPath`` require:
* `PyObjC <https://pypi.python.org/pypi/pyobjc>`__: the bridge between
Python and the Objective-C runtime (macOS platform only).
@@ -298,79 +205,22 @@ are required to unlock the extra features named "ufo", etc.
* `reportlab <https://pypi.python.org/pypi/reportlab>`__: Python toolkit
for generating PDFs and graphics.
-Testing
-~~~~~~~
-
-To run the test suite, you need to install `pytest <http://docs.pytest.org/en/latest/>`__.
-When you run the ``pytest`` command, the tests will run against the
-installed ``fontTools`` package, or the first one found in the
-``PYTHONPATH``.
-
-You can also use `tox <https://tox.readthedocs.io/en/latest/>`__ to
-automatically run tests on different Python versions in isolated virtual
-environments.
-
-.. code:: sh
-
- pip install tox
- tox
-
-Note that when you run ``tox`` without arguments, the tests are executed
-for all the environments listed in tox.ini's ``envlist``. In our case,
-this includes Python 2.7 and 3.7, so for this to work the ``python2.7``
-and ``python3.7`` executables must be available in your ``PATH``.
-
-You can specify an alternative environment list via the ``-e`` option,
-or the ``TOXENV`` environment variable:
-
-.. code:: sh
-
- tox -e py27
- TOXENV="py36-cov,htmlcov" tox
-
-Development Community
-~~~~~~~~~~~~~~~~~~~~~
-
-TTX/FontTools development is ongoing in an active community of
-developers, that includes professional developers employed at major
-software corporations and type foundries as well as hobbyists.
-
-Feature requests and bug reports are always welcome at
-https://github.com/fonttools/fonttools/issues/
-
-The best place for discussions about TTX from an end-user perspective as
-well as TTX/FontTools development is the
-https://groups.google.com/d/forum/fonttools mailing list. There is also
-a development https://groups.google.com/d/forum/fonttools-dev mailing
-list for continuous integration notifications. You can also email Behdad
-privately at behdad@behdad.org
-
-History
-~~~~~~~
-
-The fontTools project was started by Just van Rossum in 1999, and was
-maintained as an open source project at
-http://sourceforge.net/projects/fonttools/. In 2008, Paul Wise (pabs3)
-began helping Just with stability maintenance. In 2013 Behdad Esfahbod
-began a friendly fork, thoroughly reviewing the codebase and making
-changes at https://github.com/behdad/fonttools to add new features and
-support for new font formats.
-
Acknowledgements
~~~~~~~~~~~~~~~~
In alphabetical order:
Olivier Berten, Samyak Bhuta, Erik van Blokland, Petr van Blokland,
-Jelle Bosma, Sascha Brawer, Tom Byrer, Frédéric Coiffier, Vincent
-Connare, Dave Crossland, Simon Daniels, Peter Dekkers, Behdad Esfahbod,
-Behnam Esfahbod, Hannes Famira, Sam Fishman, Matt Fontaine, Yannis
-Haralambous, Greg Hitchcock, Jeremie Hornus, Khaled Hosny, John Hudson,
-Denis Moyogo Jacquerye, Jack Jansen, Tom Kacvinsky, Jens Kutilek,
-Antoine Leca, Werner Lemberg, Tal Leming, Peter Lofting, Cosimo Lupo,
-Masaya Nakamura, Dave Opstad, Laurence Penney, Roozbeh Pournader, Garret
-Rieger, Read Roberts, Guido van Rossum, Just van Rossum, Andreas Seidel,
-Georg Seifert, Miguel Sousa, Adam Twardoch, Adrien Tétar, Vitaly Volkov,
+Jelle Bosma, Sascha Brawer, Tom Byrer, Antonio Cavedoni, Frédéric
+Coiffier, Vincent Connare, David Corbett, Simon Cozens, Dave Crossland,
+Simon Daniels, Peter Dekkers, Behdad Esfahbod, Behnam Esfahbod, Hannes
+Famira, Sam Fishman, Matt Fontaine, Yannis Haralambous, Greg Hitchcock,
+Jeremie Hornus, Khaled Hosny, John Hudson, Denis Moyogo Jacquerye, Jack
+Jansen, Tom Kacvinsky, Jens Kutilek, Antoine Leca, Werner Lemberg, Tal
+Leming, Peter Lofting, Cosimo Lupo, Masaya Nakamura, Dave Opstad,
+Laurence Penney, Roozbeh Pournader, Garret Rieger, Read Roberts, Guido
+van Rossum, Just van Rossum, Andreas Seidel, Georg Seifert, Chris
+Simpkins, Miguel Sousa, Adam Twardoch, Adrien Tétar, Vitaly Volkov,
Paul Wise.
Copyrights
@@ -390,10 +240,8 @@ Rights Reserved.
Have fun!
-.. |Travis Build Status| image:: https://travis-ci.org/fonttools/fonttools.svg
- :target: https://travis-ci.org/fonttools/fonttools
-.. |Appveyor Build status| image:: https://ci.appveyor.com/api/projects/status/0f7fmee9as744sl7/branch/master?svg=true
- :target: https://ci.appveyor.com/project/fonttools/fonttools/branch/master
+.. |CI Build Status| image:: https://github.com/fonttools/fonttools/workflows/Test/badge.svg
+ :target: https://github.com/fonttools/fonttools/actions?query=workflow%3ATest
.. |Coverage Status| image:: https://codecov.io/gh/fonttools/fonttools/branch/master/graph/badge.svg
:target: https://codecov.io/gh/fonttools/fonttools
.. |PyPI| image:: https://img.shields.io/pypi/v/fonttools.svg
diff --git a/Snippets/cmap-format.py b/Snippets/cmap-format.py
index 0cee39c5..0a78670f 100755
--- a/Snippets/cmap-format.py
+++ b/Snippets/cmap-format.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
# Sample script to convert legacy cmap subtables to format-4
# subtables. Note that this is rarely what one needs. You
@@ -10,8 +10,6 @@
# getEncoding() of subtable and use that encoding to map the
# characters to Unicode... TODO: Extend this script to do that.
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables._c_m_a_p import CmapSubtable
import sys
diff --git a/Snippets/decompose-ttf.py b/Snippets/decompose-ttf.py
new file mode 100755
index 00000000..bccaf72e
--- /dev/null
+++ b/Snippets/decompose-ttf.py
@@ -0,0 +1,53 @@
+#! /usr/bin/env python3
+
+# Example script to decompose the composite glyphs in a TTF into
+# non-composite outlines.
+
+
+import sys
+from fontTools.ttLib import TTFont
+from fontTools.pens.recordingPen import DecomposingRecordingPen
+from fontTools.pens.ttGlyphPen import TTGlyphPen
+
+try:
+ import pathops
+except ImportError:
+ sys.exit(
+ "This script requires the skia-pathops module. "
+ "`pip install skia-pathops` and then retry."
+ )
+
+
+if len(sys.argv) != 3:
+ print("usage: decompose-ttf.py fontfile.ttf outfile.ttf")
+ sys.exit(1)
+
+src = sys.argv[1]
+dst = sys.argv[2]
+
+with TTFont(src) as f:
+ glyfTable = f["glyf"]
+ glyphSet = f.getGlyphSet()
+
+ for glyphName in glyphSet.keys():
+ if not glyfTable[glyphName].isComposite():
+ continue
+
+ # record TTGlyph outlines without components
+ dcPen = DecomposingRecordingPen(glyphSet)
+ glyphSet[glyphName].draw(dcPen)
+
+ # replay recording onto a skia-pathops Path
+ path = pathops.Path()
+ pathPen = path.getPen()
+ dcPen.replay(pathPen)
+
+ # remove overlaps
+ path.simplify()
+
+ # create new TTGlyph from Path
+ ttPen = TTGlyphPen(None)
+ path.draw(ttPen)
+ glyfTable[glyphName] = ttPen.glyph()
+
+ f.save(dst)
diff --git a/Snippets/dump_woff_metadata.py b/Snippets/dump_woff_metadata.py
index 0023a106..c9ea574f 100644
--- a/Snippets/dump_woff_metadata.py
+++ b/Snippets/dump_woff_metadata.py
@@ -1,4 +1,3 @@
-from __future__ import print_function
import sys
from fontTools.ttx import makeOutputFileName
from fontTools.ttLib import TTFont
diff --git a/Snippets/fix-dflt-langsys.py b/Snippets/fix-dflt-langsys.py
index d8eccb44..c072117a 100644
--- a/Snippets/fix-dflt-langsys.py
+++ b/Snippets/fix-dflt-langsys.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import argparse
import logging
diff --git a/Snippets/fontTools b/Snippets/fontTools
new file mode 100755
index 00000000..9a21c02e
--- /dev/null
+++ b/Snippets/fontTools
@@ -0,0 +1 @@
+../Lib/fontTools \ No newline at end of file
diff --git a/Snippets/interpolate.py b/Snippets/interpolate.py
index 7ed822d2..063046c9 100755
--- a/Snippets/interpolate.py
+++ b/Snippets/interpolate.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
# Illustrates how a fonttools script can construct variable fonts.
#
@@ -21,8 +21,6 @@
# $ ./interpolate.py && open Roboto.ttf
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables._n_a_m_e import NameRecord
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance
diff --git a/Snippets/layout-features.py b/Snippets/layout-features.py
index 25522cda..53e97355 100755
--- a/Snippets/layout-features.py
+++ b/Snippets/layout-features.py
@@ -1,7 +1,5 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables import otTables
import sys
diff --git a/Snippets/merge_woff_metadata.py b/Snippets/merge_woff_metadata.py
index 669de461..d6e858f2 100644
--- a/Snippets/merge_woff_metadata.py
+++ b/Snippets/merge_woff_metadata.py
@@ -1,4 +1,3 @@
-from __future__ import print_function
import sys
import os
from fontTools.ttx import makeOutputFileName
diff --git a/Snippets/name-viewer.ipynb b/Snippets/name-viewer.ipynb
new file mode 100644
index 00000000..11722bca
--- /dev/null
+++ b/Snippets/name-viewer.ipynb
@@ -0,0 +1,117 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## name-viewer.ipynb\n",
+ "\n",
+ "### Usage\n",
+ "\n",
+ "1. Install `jupyter`, `plotly`, `fonttools` dependencies with pip\n",
+ "2. Modify the `FONT_PATH` setting in the Python source block below by clicking next to it and typing a new path in your web browser window\n",
+ "3. Execute the block of Python source by selecting the code block below and clicking the \"Run\" button above\n",
+ "4. Repeat from step 2 with modified font paths to view tables in other fonts\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import plotly as py\n",
+ "import plotly.graph_objs as go\n",
+ "\n",
+ "from fontTools.ttLib import TTFont\n",
+ "\n",
+ "py.offline.init_notebook_mode(connected=True)\n",
+ "\n",
+ "# EDIT HERE ------------------------------------------------------\n",
+ "#\n",
+ "# Path to font file\n",
+ "FONT_PATH = \"path/to/font.ttf\"\n",
+ "#\n",
+ "# Table height\n",
+ "# - adjust for the length of output from the font file\n",
+ "HEIGHT = 700\n",
+ "#\n",
+ "# END EDIT -------------------------------------------------------\n",
+ "\n",
+ "record_list = []\n",
+ "nameID_list = []\n",
+ "ppelangID_list = []\n",
+ "value_list = []\n",
+ "\n",
+ "tt = TTFont(FONT_PATH)\n",
+ "namerecord_list = tt[\"name\"].names\n",
+ "\n",
+ "for record in namerecord_list:\n",
+ " nameID_list.append(record.nameID)\n",
+ " ppelangID_list.append(\"{} {} {}\".format(record.platformID, \n",
+ " record.platEncID, \n",
+ " record.langID))\n",
+ " value_list.append(\"{}\".format(record.string.decode('utf-8').strip()))\n",
+ " \n",
+ "\n",
+ "record_list.append(nameID_list)\n",
+ "record_list.append(ppelangID_list)\n",
+ "record_list.append(value_list)\n",
+ "\n",
+ "\n",
+ "trace0 = go.Table(\n",
+ " columnorder = [1,2,3],\n",
+ " columnwidth = [80,80,400],\n",
+ " header = dict(\n",
+ " values = [['<b>nameID</b>'],\n",
+ " ['<b>p-pE-lang</b>'],\n",
+ " ['<b>Value</b>']\n",
+ " ],\n",
+ " line = dict(color = '#506784'),\n",
+ " fill = dict(color = '#FFDE00'),\n",
+ " align = ['center','center', 'center'],\n",
+ " font = dict(color = 'black', size = 16),\n",
+ " ),\n",
+ " cells = dict(\n",
+ " values = record_list,\n",
+ " line = dict(color = '#506784'),\n",
+ " fill = dict(color = ['#000000', 'white']),\n",
+ " align = ['center', 'center', 'left'],\n",
+ " font = dict(color = ['#F8F8F5', '#000000', '#000000'], size = 14),\n",
+ " height = 30,\n",
+ " ))\n",
+ "\n",
+ "data1 = [trace0]\n",
+ "\n",
+ "layout1 = go.Layout(\n",
+ " autosize=True,\n",
+ " height=HEIGHT,\n",
+ ")\n",
+ "\n",
+ "fig1 = dict(data=data1, layout=layout1)\n",
+ "py.offline.iplot(fig1)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/Snippets/otf2ttf.py b/Snippets/otf2ttf.py
index 62b4f735..b925b33c 100755
--- a/Snippets/otf2ttf.py
+++ b/Snippets/otf2ttf.py
@@ -1,12 +1,11 @@
-#!/usr/bin/env python
-from __future__ import print_function, division, absolute_import
+#!/usr/bin/env python3
import argparse
import logging
import os
import sys
-from cu2qu.pens import Cu2QuPen
+from fontTools.pens.cu2quPen import Cu2QuPen
from fontTools import configLogger
from fontTools.misc.cliTools import makeOutputFileName
from fontTools.pens.ttGlyphPen import TTGlyphPen
@@ -39,6 +38,13 @@ def glyphs_to_quadratic(
return quadGlyphs
+def update_hmtx(ttFont, glyf):
+ hmtx = ttFont["hmtx"]
+ for glyphName, glyph in glyf.glyphs.items():
+ if hasattr(glyph, 'xMin'):
+ hmtx[glyphName] = (hmtx[glyphName][0], glyph.xMin)
+
+
def otf_to_ttf(ttFont, post_format=POST_FORMAT, **kwargs):
assert ttFont.sfntVersion == "OTTO"
assert "CFF " in ttFont
@@ -51,6 +57,7 @@ def otf_to_ttf(ttFont, post_format=POST_FORMAT, **kwargs):
glyf.glyphs = glyphs_to_quadratic(ttFont.getGlyphSet(), **kwargs)
del ttFont["CFF "]
glyf.compile(ttFont)
+ update_hmtx(ttFont, glyf)
ttFont["maxp"] = maxp = newTable("maxp")
maxp.tableVersion = 0x00010000
diff --git a/Snippets/rename-fonts.py b/Snippets/rename-fonts.py
index ddfce103..0a43dc2a 100755
--- a/Snippets/rename-fonts.py
+++ b/Snippets/rename-fonts.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""Script to add a suffix to all family names in the input font's `name` table,
and to optionally rename the output files with the given suffix.
@@ -6,7 +6,6 @@ The current family name substring is searched in the nameIDs 1, 3, 4, 6, 16,
and 21, and if found the suffix is inserted after it; or else the suffix is
appended at the end.
"""
-from __future__ import print_function, absolute_import, unicode_literals
import os
import argparse
import logging
diff --git a/Snippets/subset-fpgm.py b/Snippets/subset-fpgm.py
index c20c05fc..d06c3f5f 100755
--- a/Snippets/subset-fpgm.py
+++ b/Snippets/subset-fpgm.py
@@ -1,7 +1,5 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
import sys
diff --git a/Snippets/svg2glif.py b/Snippets/svg2glif.py
index aed31005..22fcc7d1 100755
--- a/Snippets/svg2glif.py
+++ b/Snippets/svg2glif.py
@@ -1,9 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
""" Convert SVG paths to UFO glyphs. """
-from __future__ import print_function, absolute_import
-__requires__ = ["FontTools", "ufoLib"]
+__requires__ = ["fontTools"]
from fontTools.misc.py23 import SimpleNamespace
from fontTools.svgLib import SVGPath
diff --git a/Tests/afmLib/afmLib_test.py b/Tests/afmLib/afmLib_test.py
index 97fcf8d9..3e9d9d88 100644
--- a/Tests/afmLib/afmLib_test.py
+++ b/Tests/afmLib/afmLib_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import unittest
import os
from fontTools import afmLib
diff --git a/Tests/agl_test.py b/Tests/agl_test.py
index 8754e6a2..f2fb72d0 100644
--- a/Tests/agl_test.py
+++ b/Tests/agl_test.py
@@ -1,7 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import (print_function, division, absolute_import,
- unicode_literals)
-from fontTools.misc.py23 import *
from fontTools import agl
import unittest
@@ -9,21 +5,15 @@ import unittest
class AglToUnicodeTest(unittest.TestCase):
def test_spec_examples(self):
# https://github.com/adobe-type-tools/agl-specification#3-examples
- #
- # TODO: Currently, we only handle AGLFN instead of legacy AGL names.
- # Therefore, the test cases below use Iogonek instead of Lcommaaccent.
- # Change Iogonek to Lcommaaccent as soon as the implementation has
- # been fixed to also support legacy AGL names.
- # https://github.com/fonttools/fonttools/issues/775
- self.assertEqual(agl.toUnicode("Iogonek"), "Į")
+ self.assertEqual(agl.toUnicode("Lcommaaccent"), "Ļ")
self.assertEqual(agl.toUnicode("uni20AC0308"), "\u20AC\u0308")
self.assertEqual(agl.toUnicode("u1040C"), "\U0001040C")
self.assertEqual(agl.toUnicode("uniD801DC0C"), "")
self.assertEqual(agl.toUnicode("uni20ac"), "")
self.assertEqual(
- agl.toUnicode("Iogonek_uni20AC0308_u1040C.alternate"),
- "\u012E\u20AC\u0308\U0001040C")
- self.assertEqual(agl.toUnicode("Iogonek_uni012E_u012E"), "ĮĮĮ")
+ agl.toUnicode("Lcommaaccent_uni20AC0308_u1040C.alternate"),
+ "\u013B\u20AC\u0308\U0001040C")
+ self.assertEqual(agl.toUnicode("Lcommaaccent_uni013B_u013B"), "ĻĻĻ")
self.assertEqual(agl.toUnicode("foo"), "")
self.assertEqual(agl.toUnicode(".notdef"), "")
diff --git a/Tests/cffLib/cffLib_test.py b/Tests/cffLib/cffLib_test.py
index cc9d3365..7a6e9216 100644
--- a/Tests/cffLib/cffLib_test.py
+++ b/Tests/cffLib/cffLib_test.py
@@ -1,4 +1,3 @@
-from __future__ import print_function, division, absolute_import
from fontTools.cffLib import TopDict, PrivateDict, CharStrings
from fontTools.misc.testTools import parseXML, DataFilesHandler
from fontTools.ttLib import TTFont
@@ -93,6 +92,21 @@ class CffLibTest(DataFilesHandler):
self.assertEqual(topDict2.FDSelect.format, 4)
self.assertEqual(topDict2.FDSelect.gidArray, [0, 0, 1])
+ def test_unique_glyph_names(self):
+ font_path = self.getpath('LinLibertine_RBI.otf')
+ font = TTFont(font_path, recalcBBoxes=False, recalcTimestamp=False)
+
+ glyphOrder = font.getGlyphOrder()
+ self.assertEqual(len(glyphOrder), len(set(glyphOrder)))
+
+ self.temp_dir()
+ save_path = os.path.join(self.tempdir, 'TestOTF.otf')
+ font.save(save_path)
+
+ font2 = TTFont(save_path)
+ glyphOrder = font2.getGlyphOrder()
+ self.assertEqual(len(glyphOrder), len(set(glyphOrder)))
+
if __name__ == "__main__":
sys.exit(unittest.main())
diff --git a/Tests/cffLib/data/LinLibertine_RBI.otf b/Tests/cffLib/data/LinLibertine_RBI.otf
new file mode 100755
index 00000000..c1a4ff72
--- /dev/null
+++ b/Tests/cffLib/data/LinLibertine_RBI.otf
Binary files differ
diff --git a/Tests/cffLib/data/TestCFF2Widths.ttx b/Tests/cffLib/data/TestCFF2Widths.ttx
index bbac612b..e3a3c9c1 100644
--- a/Tests/cffLib/data/TestCFF2Widths.ttx
+++ b/Tests/cffLib/data/TestCFF2Widths.ttx
@@ -375,7 +375,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="B" class="1"/>
</GlyphClassDef>
diff --git a/Tests/cffLib/specializer_test.py b/Tests/cffLib/specializer_test.py
index d45782ec..a9b778c0 100644
--- a/Tests/cffLib/specializer_test.py
+++ b/Tests/cffLib/specializer_test.py
@@ -1,4 +1,3 @@
-from __future__ import print_function, division, absolute_import
from fontTools.cffLib.specializer import (programToString, stringToProgram,
generalizeProgram, specializeProgram,
programToCommands, commandsToProgram,
diff --git a/Tests/colorLib/__init__.py b/Tests/colorLib/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/Tests/colorLib/__init__.py
diff --git a/Tests/colorLib/builder_test.py b/Tests/colorLib/builder_test.py
new file mode 100644
index 00000000..81da2818
--- /dev/null
+++ b/Tests/colorLib/builder_test.py
@@ -0,0 +1,1662 @@
+from fontTools.ttLib import newTable
+from fontTools.ttLib.tables import otTables as ot
+from fontTools.colorLib import builder
+from fontTools.colorLib.geometry import round_start_circle_stable_containment, Circle
+from fontTools.colorLib.builder import LayerV1ListBuilder, _build_n_ary_tree
+from fontTools.colorLib.table_builder import TableBuilder
+from fontTools.colorLib.errors import ColorLibError
+import pytest
+from typing import List
+
+
+def _build(cls, source):
+ return LayerV1ListBuilder().tableBuilder.build(cls, source)
+
+
+def _buildPaint(source):
+ return LayerV1ListBuilder().buildPaint(source)
+
+
+def test_buildCOLR_v0():
+ color_layer_lists = {
+ "a": [("a.color0", 0), ("a.color1", 1)],
+ "b": [("b.color1", 1), ("b.color0", 0)],
+ }
+
+ colr = builder.buildCOLR(color_layer_lists)
+
+ assert colr.tableTag == "COLR"
+ assert colr.version == 0
+ assert colr.ColorLayers["a"][0].name == "a.color0"
+ assert colr.ColorLayers["a"][0].colorID == 0
+ assert colr.ColorLayers["a"][1].name == "a.color1"
+ assert colr.ColorLayers["a"][1].colorID == 1
+ assert colr.ColorLayers["b"][0].name == "b.color1"
+ assert colr.ColorLayers["b"][0].colorID == 1
+ assert colr.ColorLayers["b"][1].name == "b.color0"
+ assert colr.ColorLayers["b"][1].colorID == 0
+
+
+def test_buildCOLR_v0_layer_as_list():
+ # when COLRv0 layers are encoded as plist in UFO lib, both python tuples and
+ # lists are encoded as plist array elements; but the latter are always decoded
+ # as python lists, thus after roundtripping a plist tuples become lists.
+ # Before FontTools 4.17.0 we were treating tuples and lists as equivalent;
+ # with 4.17.0, a paint of type list is used to identify a PaintColrLayers.
+ # This broke backward compatibility as ufo2ft is simply passing through the
+ # color layers as read from the UFO lib plist, and as such the latter use lists
+ # instead of tuples for COLRv0 layers (layerGlyph, paletteIndex) combo.
+ # We restore backward compat by accepting either tuples or lists (of length 2
+ # and only containing a str and an int) as individual top-level layers.
+ # https://github.com/googlefonts/ufo2ft/issues/426
+ color_layer_lists = {
+ "a": [["a.color0", 0], ["a.color1", 1]],
+ "b": [["b.color1", 1], ["b.color0", 0]],
+ }
+
+ colr = builder.buildCOLR(color_layer_lists)
+
+ assert colr.tableTag == "COLR"
+ assert colr.version == 0
+ assert colr.ColorLayers["a"][0].name == "a.color0"
+ assert colr.ColorLayers["a"][0].colorID == 0
+ assert colr.ColorLayers["a"][1].name == "a.color1"
+ assert colr.ColorLayers["a"][1].colorID == 1
+ assert colr.ColorLayers["b"][0].name == "b.color1"
+ assert colr.ColorLayers["b"][0].colorID == 1
+ assert colr.ColorLayers["b"][1].name == "b.color0"
+ assert colr.ColorLayers["b"][1].colorID == 0
+
+
+def test_buildCPAL_v0():
+ palettes = [
+ [(0.68, 0.20, 0.32, 1.0), (0.45, 0.68, 0.21, 1.0)],
+ [(0.68, 0.20, 0.32, 0.6), (0.45, 0.68, 0.21, 0.6)],
+ [(0.68, 0.20, 0.32, 0.3), (0.45, 0.68, 0.21, 0.3)],
+ ]
+
+ cpal = builder.buildCPAL(palettes)
+
+ assert cpal.tableTag == "CPAL"
+ assert cpal.version == 0
+ assert cpal.numPaletteEntries == 2
+
+ assert len(cpal.palettes) == 3
+ assert [tuple(c) for c in cpal.palettes[0]] == [
+ (82, 51, 173, 255),
+ (54, 173, 115, 255),
+ ]
+ assert [tuple(c) for c in cpal.palettes[1]] == [
+ (82, 51, 173, 153),
+ (54, 173, 115, 153),
+ ]
+ assert [tuple(c) for c in cpal.palettes[2]] == [
+ (82, 51, 173, 76),
+ (54, 173, 115, 76),
+ ]
+
+
+def test_buildCPAL_palettes_different_lengths():
+ with pytest.raises(ColorLibError, match="have different lengths"):
+ builder.buildCPAL([[(1, 1, 1, 1)], [(0, 0, 0, 1), (0.5, 0.5, 0.5, 1)]])
+
+
+def test_buildPaletteLabels():
+ name_table = newTable("name")
+ name_table.names = []
+
+ name_ids = builder.buildPaletteLabels(
+ [None, "hi", {"en": "hello", "de": "hallo"}], name_table
+ )
+
+ assert name_ids == [0xFFFF, 256, 257]
+
+ assert len(name_table.names) == 3
+ assert str(name_table.names[0]) == "hi"
+ assert name_table.names[0].nameID == 256
+
+ assert str(name_table.names[1]) == "hallo"
+ assert name_table.names[1].nameID == 257
+
+ assert str(name_table.names[2]) == "hello"
+ assert name_table.names[2].nameID == 257
+
+
+def test_build_CPAL_v1_types_no_labels():
+ palettes = [
+ [(0.1, 0.2, 0.3, 1.0), (0.4, 0.5, 0.6, 1.0)],
+ [(0.1, 0.2, 0.3, 0.6), (0.4, 0.5, 0.6, 0.6)],
+ [(0.1, 0.2, 0.3, 0.3), (0.4, 0.5, 0.6, 0.3)],
+ ]
+ paletteTypes = [
+ builder.ColorPaletteType.USABLE_WITH_LIGHT_BACKGROUND,
+ builder.ColorPaletteType.USABLE_WITH_DARK_BACKGROUND,
+ builder.ColorPaletteType.USABLE_WITH_LIGHT_BACKGROUND
+ | builder.ColorPaletteType.USABLE_WITH_DARK_BACKGROUND,
+ ]
+
+ cpal = builder.buildCPAL(palettes, paletteTypes=paletteTypes)
+
+ assert cpal.tableTag == "CPAL"
+ assert cpal.version == 1
+ assert cpal.numPaletteEntries == 2
+ assert len(cpal.palettes) == 3
+
+ assert cpal.paletteTypes == paletteTypes
+ assert cpal.paletteLabels == [cpal.NO_NAME_ID] * len(palettes)
+ assert cpal.paletteEntryLabels == [cpal.NO_NAME_ID] * cpal.numPaletteEntries
+
+
+def test_build_CPAL_v1_labels():
+ palettes = [
+ [(0.1, 0.2, 0.3, 1.0), (0.4, 0.5, 0.6, 1.0)],
+ [(0.1, 0.2, 0.3, 0.6), (0.4, 0.5, 0.6, 0.6)],
+ [(0.1, 0.2, 0.3, 0.3), (0.4, 0.5, 0.6, 0.3)],
+ ]
+ paletteLabels = ["First", {"en": "Second", "it": "Seconda"}, None]
+ paletteEntryLabels = ["Foo", "Bar"]
+
+ with pytest.raises(TypeError, match="nameTable is required"):
+ builder.buildCPAL(palettes, paletteLabels=paletteLabels)
+ with pytest.raises(TypeError, match="nameTable is required"):
+ builder.buildCPAL(palettes, paletteEntryLabels=paletteEntryLabels)
+
+ name_table = newTable("name")
+ name_table.names = []
+
+ cpal = builder.buildCPAL(
+ palettes,
+ paletteLabels=paletteLabels,
+ paletteEntryLabels=paletteEntryLabels,
+ nameTable=name_table,
+ )
+
+ assert cpal.tableTag == "CPAL"
+ assert cpal.version == 1
+ assert cpal.numPaletteEntries == 2
+ assert len(cpal.palettes) == 3
+
+ assert cpal.paletteTypes == [cpal.DEFAULT_PALETTE_TYPE] * len(palettes)
+ assert cpal.paletteLabels == [256, 257, cpal.NO_NAME_ID]
+ assert cpal.paletteEntryLabels == [258, 259]
+
+ assert name_table.getDebugName(256) == "First"
+ assert name_table.getDebugName(257) == "Second"
+ assert name_table.getDebugName(258) == "Foo"
+ assert name_table.getDebugName(259) == "Bar"
+
+
+def test_invalid_ColorPaletteType():
+ with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
+ builder.ColorPaletteType(-1)
+ with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
+ builder.ColorPaletteType(4)
+ with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
+ builder.ColorPaletteType("abc")
+
+
+def test_buildCPAL_v1_invalid_args_length():
+ with pytest.raises(ColorLibError, match="Expected 2 paletteTypes, got 1"):
+ builder.buildCPAL([[(0, 0, 0, 0)], [(1, 1, 1, 1)]], paletteTypes=[1])
+
+ with pytest.raises(ColorLibError, match="Expected 2 paletteLabels, got 1"):
+ builder.buildCPAL(
+ [[(0, 0, 0, 0)], [(1, 1, 1, 1)]],
+ paletteLabels=["foo"],
+ nameTable=newTable("name"),
+ )
+
+ with pytest.raises(ColorLibError, match="Expected 1 paletteEntryLabels, got 0"):
+ cpal = builder.buildCPAL(
+ [[(0, 0, 0, 0)], [(1, 1, 1, 1)]],
+ paletteEntryLabels=[],
+ nameTable=newTable("name"),
+ )
+
+
+def test_buildCPAL_invalid_color():
+ with pytest.raises(
+ ColorLibError,
+ match=r"In palette\[0\]\[1\]: expected \(R, G, B, A\) tuple, got \(1, 1, 1\)",
+ ):
+ builder.buildCPAL([[(1, 1, 1, 1), (1, 1, 1)]])
+
+ with pytest.raises(
+ ColorLibError,
+ match=(
+ r"palette\[1\]\[0\] has invalid out-of-range "
+ r"\[0..1\] color: \(1, 1, -1, 2\)"
+ ),
+ ):
+ builder.buildCPAL([[(0, 0, 0, 0)], [(1, 1, -1, 2)]])
+
+
+def test_buildColorIndex_Minimal():
+ c = _build(ot.ColorIndex, 1)
+ assert c.PaletteIndex == 1
+ assert c.Alpha == 1.0
+
+
+def test_buildVarColorIndex_Minimal():
+ c = _build(ot.VarColorIndex, 1)
+ assert c.PaletteIndex == 1
+ assert c.Alpha.value == 1.0
+ assert c.Alpha.varIdx == 0
+
+
+def test_buildColorIndex():
+ c = _build(ot.ColorIndex, (1, 0.5))
+ assert c.PaletteIndex == 1
+ assert c.Alpha == 0.5
+
+
+def test_buildVarColorIndex():
+ c = _build(ot.VarColorIndex, (3, builder.VariableFloat(0.5, varIdx=2)))
+ assert c.PaletteIndex == 3
+ assert c.Alpha.value == 0.5
+ assert c.Alpha.varIdx == 2
+
+
+def test_buildPaintSolid():
+ p = _buildPaint((ot.PaintFormat.PaintSolid, 0))
+ assert p.Format == ot.PaintFormat.PaintSolid
+ assert p.Color.PaletteIndex == 0
+ assert p.Color.Alpha == 1.0
+
+
+def test_buildPaintSolid_Alpha():
+ p = _buildPaint((ot.PaintFormat.PaintSolid, (1, 0.5)))
+ assert p.Format == ot.PaintFormat.PaintSolid
+ assert p.Color.PaletteIndex == 1
+ assert p.Color.Alpha == 0.5
+
+
+def test_buildPaintVarSolid():
+ p = _buildPaint(
+ (ot.PaintFormat.PaintVarSolid, (3, builder.VariableFloat(0.5, varIdx=2)))
+ )
+ assert p.Format == ot.PaintFormat.PaintVarSolid
+ assert p.Color.PaletteIndex == 3
+ assert p.Color.Alpha.value == 0.5
+ assert p.Color.Alpha.varIdx == 2
+
+
+def test_buildVarColorStop_DefaultAlpha():
+ s = _build(ot.ColorStop, (0.1, 2))
+ assert s.StopOffset == 0.1
+ assert s.Color.PaletteIndex == 2
+ assert s.Color.Alpha == builder._DEFAULT_ALPHA.value
+
+
+def test_buildVarColorStop_DefaultAlpha():
+ s = _build(ot.VarColorStop, (0.1, 2))
+ assert s.StopOffset == builder.VariableFloat(0.1)
+ assert s.Color.PaletteIndex == 2
+ assert s.Color.Alpha == builder._DEFAULT_ALPHA
+
+
+def test_buildColorStop():
+ s = _build(
+ ot.ColorStop, {"StopOffset": 0.2, "Color": {"PaletteIndex": 3, "Alpha": 0.4}}
+ )
+ assert s.StopOffset == 0.2
+ assert s.Color == _build(ot.ColorIndex, (3, 0.4))
+
+
+def test_buildColorStop_Variable():
+ s = _build(
+ ot.VarColorStop,
+ {
+ "StopOffset": builder.VariableFloat(0.0, varIdx=1),
+ "Color": {
+ "PaletteIndex": 0,
+ "Alpha": builder.VariableFloat(0.3, varIdx=2),
+ },
+ },
+ )
+ assert s.StopOffset == builder.VariableFloat(0.0, varIdx=1)
+ assert s.Color.PaletteIndex == 0
+ assert s.Color.Alpha == builder.VariableFloat(0.3, varIdx=2)
+
+
+def test_buildColorLine_StopList():
+ stops = [(0.0, 0), (0.5, 1), (1.0, 2)]
+
+ cline = _build(ot.ColorLine, {"ColorStop": stops})
+ assert cline.Extend == builder.ExtendMode.PAD
+ assert cline.StopCount == 3
+ assert [(cs.StopOffset, cs.Color.PaletteIndex) for cs in cline.ColorStop] == stops
+
+ cline = _build(ot.ColorLine, {"Extend": "pad", "ColorStop": stops})
+ assert cline.Extend == builder.ExtendMode.PAD
+
+ cline = _build(
+ ot.ColorLine, {"ColorStop": stops, "Extend": builder.ExtendMode.REPEAT}
+ )
+ assert cline.Extend == builder.ExtendMode.REPEAT
+
+ cline = _build(
+ ot.ColorLine, {"ColorStop": stops, "Extend": builder.ExtendMode.REFLECT}
+ )
+ assert cline.Extend == builder.ExtendMode.REFLECT
+
+ cline = _build(
+ ot.ColorLine, {"ColorStop": [_build(ot.ColorStop, s) for s in stops]}
+ )
+ assert [(cs.StopOffset, cs.Color.PaletteIndex) for cs in cline.ColorStop] == stops
+
+
+def test_buildVarColorLine_StopMap():
+ stops = [
+ {"StopOffset": (0.0, (1,)), "Color": {"PaletteIndex": 0, "Alpha": (0.5, 2)}},
+ {"StopOffset": (1.0, (3,)), "Color": {"PaletteIndex": 1, "Alpha": (0.3, 4)}},
+ ]
+ cline = _build(ot.VarColorLine, {"ColorStop": stops})
+ assert [
+ {
+ "StopOffset": cs.StopOffset,
+ "Color": {
+ "PaletteIndex": cs.Color.PaletteIndex,
+ "Alpha": cs.Color.Alpha,
+ },
+ }
+ for cs in cline.ColorStop
+ ] == stops
+
+
+def checkBuildAffine2x3(cls, resultMapFn):
+ matrix = _build(cls, (1.5, 0, 0.5, 2.0, 1.0, -3.0))
+ assert matrix.xx == resultMapFn(1.5)
+ assert matrix.yx == resultMapFn(0.0)
+ assert matrix.xy == resultMapFn(0.5)
+ assert matrix.yy == resultMapFn(2.0)
+ assert matrix.dx == resultMapFn(1.0)
+ assert matrix.dy == resultMapFn(-3.0)
+
+
+def test_buildAffine2x3():
+ checkBuildAffine2x3(ot.Affine2x3, lambda v: v)
+
+
+def test_buildVarAffine2x3():
+ checkBuildAffine2x3(ot.VarAffine2x3, builder.VariableFloat)
+
+
+def _sample_stops(cls):
+ return [
+ _build(cls, (0.0, 0)),
+ _build(cls, (0.5, 1)),
+ _build(cls, (1.0, (2, 0.8))),
+ ]
+
+
+def _is_var(fmt):
+ return fmt.name.startswith("PaintVar")
+
+
+def checkBuildPaintLinearGradient(fmt):
+ if _is_var(fmt):
+ inputMapFn = builder.VariableInt
+ outputMapFn = lambda v: v.value
+ color_stops = _sample_stops(ot.VarColorStop)
+ else:
+ inputMapFn = outputMapFn = lambda v: v
+ color_stops = _sample_stops(ot.ColorStop)
+
+ x0, y0, x1, y1, x2, y2 = tuple(inputMapFn(v) for v in (1, 2, 3, 4, 5, 6))
+ gradient = _buildPaint(
+ {
+ "Format": fmt,
+ "ColorLine": {"ColorStop": color_stops},
+ "x0": x0,
+ "y0": y0,
+ "x1": x1,
+ "y1": y1,
+ "x2": x2,
+ "y2": y2,
+ },
+ )
+ assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
+ assert gradient.ColorLine.ColorStop == color_stops
+
+ gradient = _buildPaint(gradient)
+ assert (outputMapFn(gradient.x0), outputMapFn(gradient.y0)) == (1, 2)
+ assert (outputMapFn(gradient.x1), outputMapFn(gradient.y1)) == (3, 4)
+ assert (outputMapFn(gradient.x2), outputMapFn(gradient.y2)) == (5, 6)
+
+
+def test_buildPaintLinearGradient():
+ assert not _is_var(ot.PaintFormat.PaintLinearGradient)
+ checkBuildPaintLinearGradient(ot.PaintFormat.PaintLinearGradient)
+
+
+def test_buildVarPaintLinearGradient():
+ assert _is_var(ot.PaintFormat.PaintVarLinearGradient)
+ checkBuildPaintLinearGradient(ot.PaintFormat.PaintVarLinearGradient)
+
+
+def checkBuildPaintRadialGradient(fmt):
+ if _is_var(fmt):
+ inputMapFn = builder.VariableInt
+ outputMapFn = lambda v: v
+ color_stops = _sample_stops(ot.VarColorStop)
+ line_cls = ot.VarColorLine
+ else:
+ inputMapFn = outputMapFn = lambda v: v
+ color_stops = _sample_stops(ot.ColorStop)
+ line_cls = ot.ColorLine
+
+ color_line = _build(
+ line_cls, {"ColorStop": color_stops, "Extend": builder.ExtendMode.REPEAT}
+ )
+ c0 = (inputMapFn(100), inputMapFn(200))
+ c1 = (inputMapFn(150), inputMapFn(250))
+ r0 = inputMapFn(10)
+ r1 = inputMapFn(5)
+
+ gradient = _build(ot.Paint, (fmt, color_line, *c0, r0, *c1, r1))
+ assert gradient.Format == fmt
+ assert gradient.ColorLine == color_line
+ assert (outputMapFn(gradient.x0), outputMapFn(gradient.y0)) == c0
+ assert (outputMapFn(gradient.x1), outputMapFn(gradient.y1)) == c1
+ assert outputMapFn(gradient.r0) == r0
+ assert outputMapFn(gradient.r1) == r1
+
+ gradient = _build(
+ ot.Paint,
+ {
+ "Format": fmt,
+ "ColorLine": {"ColorStop": color_stops},
+ "x0": c0[0],
+ "y0": c0[1],
+ "x1": c1[0],
+ "y1": c1[1],
+ "r0": r0,
+ "r1": r1,
+ },
+ )
+ assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
+ assert gradient.ColorLine.ColorStop == color_stops
+ assert (outputMapFn(gradient.x0), outputMapFn(gradient.y0)) == c0
+ assert (outputMapFn(gradient.x1), outputMapFn(gradient.y1)) == c1
+ assert outputMapFn(gradient.r0) == r0
+ assert outputMapFn(gradient.r1) == r1
+
+
+def test_buildPaintRadialGradient():
+ assert not _is_var(ot.PaintFormat.PaintRadialGradient)
+ checkBuildPaintRadialGradient(ot.PaintFormat.PaintRadialGradient)
+
+
+def test_buildPaintVarRadialGradient():
+ assert _is_var(ot.PaintFormat.PaintVarRadialGradient)
+ checkBuildPaintRadialGradient(ot.PaintFormat.PaintVarRadialGradient)
+
+
+def checkPaintSweepGradient(fmt):
+ if _is_var(fmt):
+ outputMapFn = lambda v: v.value
+ else:
+ outputMapFn = lambda v: v
+
+ paint = _buildPaint(
+ {
+ "Format": fmt,
+ "ColorLine": {
+ "ColorStop": (
+ (0.0, 0),
+ (0.5, 1),
+ (1.0, (2, 0.8)),
+ )
+ },
+ "centerX": 127,
+ "centerY": 129,
+ "startAngle": 15,
+ "endAngle": 42,
+ }
+ )
+
+ assert paint.Format == fmt
+ assert outputMapFn(paint.centerX) == 127
+ assert outputMapFn(paint.centerY) == 129
+ assert outputMapFn(paint.startAngle) == 15
+ assert outputMapFn(paint.endAngle) == 42
+
+
+def test_buildPaintSweepGradient():
+ assert not _is_var(ot.PaintFormat.PaintSweepGradient)
+ checkPaintSweepGradient(ot.PaintFormat.PaintSweepGradient)
+
+
+def test_buildPaintVarSweepGradient():
+ assert _is_var(ot.PaintFormat.PaintVarSweepGradient)
+ checkPaintSweepGradient(ot.PaintFormat.PaintVarSweepGradient)
+
+
+def test_buildPaintGlyph_Solid():
+ layer = _build(
+ ot.Paint,
+ (
+ ot.PaintFormat.PaintGlyph,
+ (
+ ot.PaintFormat.PaintSolid,
+ 2,
+ ),
+ "a",
+ ),
+ )
+ assert layer.Format == ot.PaintFormat.PaintGlyph
+ assert layer.Glyph == "a"
+ assert layer.Paint.Format == ot.PaintFormat.PaintSolid
+ assert layer.Paint.Color.PaletteIndex == 2
+
+ layer = _build(
+ ot.Paint,
+ (
+ ot.PaintFormat.PaintGlyph,
+ (
+ ot.PaintFormat.PaintSolid,
+ (3, 0.9),
+ ),
+ "a",
+ ),
+ )
+ assert layer.Paint.Format == ot.PaintFormat.PaintSolid
+ assert layer.Paint.Color.PaletteIndex == 3
+ assert layer.Paint.Color.Alpha == 0.9
+
+
+def test_buildPaintGlyph_VarLinearGradient():
+ layer = _build(
+ ot.Paint,
+ {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Glyph": "a",
+ "Paint": {
+ "Format": ot.PaintFormat.PaintVarLinearGradient,
+ "ColorLine": {"ColorStop": [(0.0, 3), (1.0, 4)]},
+ "x0": 100,
+ "y0": 200,
+ "x1": 150,
+ "y1": 250,
+ },
+ },
+ )
+
+ assert layer.Format == ot.PaintFormat.PaintGlyph
+ assert layer.Glyph == "a"
+ assert layer.Paint.Format == ot.PaintFormat.PaintVarLinearGradient
+ assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
+ assert layer.Paint.ColorLine.ColorStop[0].Color.PaletteIndex == 3
+ assert layer.Paint.ColorLine.ColorStop[1].StopOffset.value == 1.0
+ assert layer.Paint.ColorLine.ColorStop[1].Color.PaletteIndex == 4
+ assert layer.Paint.x0.value == 100
+ assert layer.Paint.y0.value == 200
+ assert layer.Paint.x1.value == 150
+ assert layer.Paint.y1.value == 250
+
+
+def test_buildPaintGlyph_RadialGradient():
+ layer = _build(
+ ot.Paint,
+ (
+ int(ot.PaintFormat.PaintGlyph),
+ (
+ ot.PaintFormat.PaintRadialGradient,
+ (
+ "pad",
+ [
+ (0.0, 5),
+ {"StopOffset": 0.5, "Color": {"PaletteIndex": 6, "Alpha": 0.8}},
+ (1.0, 7),
+ ],
+ ),
+ 50,
+ 50,
+ 30,
+ 75,
+ 75,
+ 10,
+ ),
+ "a",
+ ),
+ )
+ assert layer.Format == ot.PaintFormat.PaintGlyph
+ assert layer.Paint.Format == ot.PaintFormat.PaintRadialGradient
+ assert layer.Paint.ColorLine.ColorStop[0].StopOffset == 0.0
+ assert layer.Paint.ColorLine.ColorStop[0].Color.PaletteIndex == 5
+ assert layer.Paint.ColorLine.ColorStop[1].StopOffset == 0.5
+ assert layer.Paint.ColorLine.ColorStop[1].Color.PaletteIndex == 6
+ assert layer.Paint.ColorLine.ColorStop[1].Color.Alpha == 0.8
+ assert layer.Paint.ColorLine.ColorStop[2].StopOffset == 1.0
+ assert layer.Paint.ColorLine.ColorStop[2].Color.PaletteIndex == 7
+ assert layer.Paint.x0 == 50
+ assert layer.Paint.y0 == 50
+ assert layer.Paint.r0 == 30
+ assert layer.Paint.x1 == 75
+ assert layer.Paint.y1 == 75
+ assert layer.Paint.r1 == 10
+
+
+def test_buildPaintGlyph_Dict_Solid():
+ layer = _build(
+ ot.Paint,
+ (
+ int(ot.PaintFormat.PaintGlyph),
+ (int(ot.PaintFormat.PaintSolid), 1),
+ "a",
+ ),
+ )
+ assert layer.Format == ot.PaintFormat.PaintGlyph
+ assert layer.Format == ot.PaintFormat.PaintGlyph
+ assert layer.Glyph == "a"
+ assert layer.Paint.Format == ot.PaintFormat.PaintSolid
+ assert layer.Paint.Color.PaletteIndex == 1
+
+
+def test_buildPaintGlyph_Dict_VarLinearGradient():
+ layer = _build(
+ ot.Paint,
+ {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Glyph": "a",
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintVarLinearGradient),
+ "ColorLine": {"ColorStop": [(0.0, 0), (1.0, 1)]},
+ "x0": 0,
+ "y0": 0,
+ "x1": 10,
+ "y1": 10,
+ },
+ },
+ )
+ assert layer.Format == ot.PaintFormat.PaintGlyph
+ assert layer.Glyph == "a"
+ assert layer.Paint.Format == ot.PaintFormat.PaintVarLinearGradient
+ assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
+
+
+def test_buildPaintGlyph_Dict_RadialGradient():
+ layer = _buildPaint(
+ {
+ "Glyph": "a",
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintRadialGradient),
+ "ColorLine": {"ColorStop": [(0.0, 0), (1.0, 1)]},
+ "x0": 0,
+ "y0": 0,
+ "r0": 4,
+ "x1": 10,
+ "y1": 10,
+ "r1": 0,
+ },
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ },
+ )
+ assert layer.Paint.Format == ot.PaintFormat.PaintRadialGradient
+ assert layer.Paint.r0 == 4
+
+
+def test_buildPaintColrGlyph():
+ paint = _buildPaint((int(ot.PaintFormat.PaintColrGlyph), "a"))
+ assert paint.Format == ot.PaintFormat.PaintColrGlyph
+ assert paint.Glyph == "a"
+
+
+def checkBuildPaintTransform(fmt):
+ if _is_var(fmt):
+ inputMapFn = builder.VariableFloat
+ outputMapFn = lambda v: v.value
+ affine_cls = ot.VarAffine2x3
+ else:
+ inputMapFn = outputMapFn = lambda v: v
+ affine_cls = ot.Affine2x3
+
+ paint = _buildPaint(
+ (
+ int(fmt),
+ (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, (0, 1.0)), "a"),
+ _build(affine_cls, (1, 2, 3, 4, 5, 6)),
+ ),
+ )
+
+ assert paint.Format == fmt
+ assert paint.Paint.Format == ot.PaintFormat.PaintGlyph
+ assert paint.Paint.Paint.Format == ot.PaintFormat.PaintSolid
+
+ assert outputMapFn(paint.Transform.xx) == 1.0
+ assert outputMapFn(paint.Transform.yx) == 2.0
+ assert outputMapFn(paint.Transform.xy) == 3.0
+ assert outputMapFn(paint.Transform.yy) == 4.0
+ assert outputMapFn(paint.Transform.dx) == 5.0
+ assert outputMapFn(paint.Transform.dy) == 6.0
+
+ paint = _build(
+ ot.Paint,
+ {
+ "Format": fmt,
+ "Transform": (1, 2, 3, 0.3333, 10, 10),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintRadialGradient),
+ "ColorLine": {"ColorStop": [(0.0, 0), (1.0, 1)]},
+ "x0": 100,
+ "y0": 101,
+ "x1": 102,
+ "y1": 103,
+ "r0": 0,
+ "r1": 50,
+ },
+ },
+ )
+
+ assert paint.Format == fmt
+ assert outputMapFn(paint.Transform.xx) == 1.0
+ assert outputMapFn(paint.Transform.yx) == 2.0
+ assert outputMapFn(paint.Transform.xy) == 3.0
+ assert outputMapFn(paint.Transform.yy) == 0.3333
+ assert outputMapFn(paint.Transform.dx) == 10
+ assert outputMapFn(paint.Transform.dy) == 10
+ assert paint.Paint.Format == ot.PaintFormat.PaintRadialGradient
+
+
+def test_buildPaintTransform():
+ assert not _is_var(ot.PaintFormat.PaintTransform)
+ checkBuildPaintTransform(ot.PaintFormat.PaintTransform)
+
+
+def test_buildPaintVarTransform():
+ assert _is_var(ot.PaintFormat.PaintVarTransform)
+ checkBuildPaintTransform(ot.PaintFormat.PaintVarTransform)
+
+
+def test_buildPaintComposite():
+ composite = _build(
+ ot.Paint,
+ {
+ "Format": int(ot.PaintFormat.PaintComposite),
+ "CompositeMode": "src_over",
+ "SourcePaint": {
+ "Format": ot.PaintFormat.PaintComposite,
+ "CompositeMode": "src_over",
+ "SourcePaint": {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Glyph": "c",
+ "Paint": (ot.PaintFormat.PaintSolid, 2),
+ },
+ "BackdropPaint": {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Glyph": "b",
+ "Paint": (ot.PaintFormat.PaintSolid, 1),
+ },
+ },
+ "BackdropPaint": {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Glyph": "a",
+ "Paint": {
+ "Format": ot.PaintFormat.PaintSolid,
+ "Color": (0, 1.0),
+ },
+ },
+ },
+ )
+
+ assert composite.Format == ot.PaintFormat.PaintComposite
+ assert composite.SourcePaint.Format == ot.PaintFormat.PaintComposite
+ assert composite.SourcePaint.SourcePaint.Format == ot.PaintFormat.PaintGlyph
+ assert composite.SourcePaint.SourcePaint.Glyph == "c"
+ assert composite.SourcePaint.SourcePaint.Paint.Format == ot.PaintFormat.PaintSolid
+ assert composite.SourcePaint.SourcePaint.Paint.Color.PaletteIndex == 2
+ assert composite.SourcePaint.CompositeMode == ot.CompositeMode.SRC_OVER
+ assert composite.SourcePaint.BackdropPaint.Format == ot.PaintFormat.PaintGlyph
+ assert composite.SourcePaint.BackdropPaint.Glyph == "b"
+ assert composite.SourcePaint.BackdropPaint.Paint.Format == ot.PaintFormat.PaintSolid
+ assert composite.SourcePaint.BackdropPaint.Paint.Color.PaletteIndex == 1
+ assert composite.CompositeMode == ot.CompositeMode.SRC_OVER
+ assert composite.BackdropPaint.Format == ot.PaintFormat.PaintGlyph
+ assert composite.BackdropPaint.Glyph == "a"
+ assert composite.BackdropPaint.Paint.Format == ot.PaintFormat.PaintSolid
+ assert composite.BackdropPaint.Paint.Color.PaletteIndex == 0
+
+
+def checkBuildPaintTranslate(fmt):
+ if _is_var(fmt):
+ inputMapFn = builder.VariableInt
+ outputMapFn = lambda v: v.value
+ else:
+ inputMapFn = outputMapFn = lambda v: v
+
+ paint = _build(
+ ot.Paint,
+ {
+ "Format": fmt,
+ "Paint": (
+ ot.PaintFormat.PaintGlyph,
+ (ot.PaintFormat.PaintSolid, (0, 1.0)),
+ "a",
+ ),
+ "dx": 123,
+ "dy": -345,
+ },
+ )
+
+ assert paint.Format == fmt
+ assert paint.Paint.Format == ot.PaintFormat.PaintGlyph
+ assert outputMapFn(paint.dx) == 123
+ assert outputMapFn(paint.dy) == -345
+
+
+def test_buildPaintTranslate():
+ assert not _is_var(ot.PaintFormat.PaintTranslate)
+ checkBuildPaintTranslate(ot.PaintFormat.PaintTranslate)
+
+
+def test_buildPaintVarTranslate():
+ assert _is_var(ot.PaintFormat.PaintVarTranslate)
+ checkBuildPaintTranslate(ot.PaintFormat.PaintVarTranslate)
+
+
+def checkBuildPaintRotate(fmt):
+ if _is_var(fmt):
+ inputMapFn = builder.VariableInt
+ outputMapFn = lambda v: v.value
+ else:
+ inputMapFn = outputMapFn = lambda v: v
+
+ paint = _build(
+ ot.Paint,
+ {
+ "Format": fmt,
+ "Paint": (
+ ot.PaintFormat.PaintGlyph,
+ (ot.PaintFormat.PaintSolid, (0, 1.0)),
+ "a",
+ ),
+ "angle": 15,
+ "centerX": 127,
+ "centerY": 129,
+ },
+ )
+
+ assert paint.Format == fmt
+ assert paint.Paint.Format == ot.PaintFormat.PaintGlyph
+ assert outputMapFn(paint.angle) == 15
+ assert outputMapFn(paint.centerX) == 127
+ assert outputMapFn(paint.centerY) == 129
+
+
+def test_buildPaintRotate():
+ assert not _is_var(ot.PaintFormat.PaintRotate)
+ checkBuildPaintRotate(ot.PaintFormat.PaintRotate)
+
+
+def test_buildPaintVarRotate():
+ assert _is_var(ot.PaintFormat.PaintVarRotate)
+ checkBuildPaintRotate(ot.PaintFormat.PaintVarRotate)
+
+
+def checkBuildPaintSkew(fmt):
+ if _is_var(fmt):
+ inputMapFn = builder.VariableInt
+ outputMapFn = lambda v: v.value
+ else:
+ inputMapFn = outputMapFn = lambda v: v
+
+ paint = _build(
+ ot.Paint,
+ {
+ "Format": fmt,
+ "Paint": (
+ ot.PaintFormat.PaintGlyph,
+ (ot.PaintFormat.PaintSolid, (0, 1.0)),
+ "a",
+ ),
+ "xSkewAngle": 15,
+ "ySkewAngle": 42,
+ "centerX": 127,
+ "centerY": 129,
+ },
+ )
+
+ assert paint.Format == fmt
+ assert paint.Paint.Format == ot.PaintFormat.PaintGlyph
+ assert outputMapFn(paint.xSkewAngle) == 15
+ assert outputMapFn(paint.ySkewAngle) == 42
+ assert outputMapFn(paint.centerX) == 127
+ assert outputMapFn(paint.centerY) == 129
+
+
+def test_buildPaintSkew():
+ assert not _is_var(ot.PaintFormat.PaintSkew)
+ checkBuildPaintSkew(ot.PaintFormat.PaintSkew)
+
+
+def test_buildPaintVarSkew():
+ assert _is_var(ot.PaintFormat.PaintVarSkew)
+ checkBuildPaintSkew(ot.PaintFormat.PaintVarSkew)
+
+
+def test_buildColrV1():
+ colorGlyphs = {
+ "a": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, 0), "b"),
+ (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintVarSolid, 1), "c"),
+ ],
+ ),
+ "d": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ (
+ ot.PaintFormat.PaintGlyph,
+ {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "Color": {"PaletteIndex": 2, "Alpha": 0.8},
+ },
+ "e",
+ ),
+ (
+ ot.PaintFormat.PaintGlyph,
+ {
+ "Format": int(ot.PaintFormat.PaintVarRadialGradient),
+ "ColorLine": {
+ "ColorStop": [(0.0, 3), (1.0, 4)],
+ "Extend": "reflect",
+ },
+ "x0": 0,
+ "y0": 0,
+ "x1": 0,
+ "y1": 0,
+ "r0": 10,
+ "r1": 0,
+ },
+ "f",
+ ),
+ ],
+ ),
+ "g": (
+ ot.PaintFormat.PaintColrLayers,
+ [(ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, 5), "h")],
+ ),
+ }
+ glyphMap = {
+ ".notdef": 0,
+ "a": 4,
+ "b": 3,
+ "c": 2,
+ "d": 1,
+ "e": 5,
+ "f": 6,
+ "g": 7,
+ "h": 8,
+ }
+
+ # TODO(anthrotype) should we split into two tests? - seems two distinct validations
+ layers, baseGlyphs = builder.buildColrV1(colorGlyphs, glyphMap)
+ assert baseGlyphs.BaseGlyphCount == len(colorGlyphs)
+ assert baseGlyphs.BaseGlyphV1Record[0].BaseGlyph == "d"
+ assert baseGlyphs.BaseGlyphV1Record[1].BaseGlyph == "a"
+ assert baseGlyphs.BaseGlyphV1Record[2].BaseGlyph == "g"
+
+ layers, baseGlyphs = builder.buildColrV1(colorGlyphs)
+ assert baseGlyphs.BaseGlyphCount == len(colorGlyphs)
+ assert baseGlyphs.BaseGlyphV1Record[0].BaseGlyph == "a"
+ assert baseGlyphs.BaseGlyphV1Record[1].BaseGlyph == "d"
+ assert baseGlyphs.BaseGlyphV1Record[2].BaseGlyph == "g"
+
+
+def test_buildColrV1_more_than_255_paints():
+ num_paints = 364
+ colorGlyphs = {
+ "a": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": (ot.PaintFormat.PaintSolid, 0),
+ "Glyph": name,
+ }
+ for name in (f"glyph{i}" for i in range(num_paints))
+ ],
+ ),
+ }
+ layers, baseGlyphs = builder.buildColrV1(colorGlyphs)
+ paints = layers.Paint
+
+ assert len(paints) == num_paints + 1
+
+ assert all(paints[i].Format == ot.PaintFormat.PaintGlyph for i in range(255))
+
+ assert paints[255].Format == ot.PaintFormat.PaintColrLayers
+ assert paints[255].FirstLayerIndex == 0
+ assert paints[255].NumLayers == 255
+
+ assert all(
+ paints[i].Format == ot.PaintFormat.PaintGlyph
+ for i in range(256, num_paints + 1)
+ )
+
+ assert baseGlyphs.BaseGlyphCount == len(colorGlyphs)
+ assert baseGlyphs.BaseGlyphV1Record[0].BaseGlyph == "a"
+ assert (
+ baseGlyphs.BaseGlyphV1Record[0].Paint.Format == ot.PaintFormat.PaintColrLayers
+ )
+ assert baseGlyphs.BaseGlyphV1Record[0].Paint.FirstLayerIndex == 255
+ assert baseGlyphs.BaseGlyphV1Record[0].Paint.NumLayers == num_paints + 1 - 255
+
+
+def test_split_color_glyphs_by_version():
+ layerBuilder = LayerV1ListBuilder()
+ colorGlyphs = {
+ "a": [
+ ("b", 0),
+ ("c", 1),
+ ("d", 2),
+ ("e", 3),
+ ]
+ }
+
+ colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
+
+ assert colorGlyphsV0 == {"a": [("b", 0), ("c", 1), ("d", 2), ("e", 3)]}
+ assert not colorGlyphsV1
+
+ colorGlyphs = {"a": (ot.PaintFormat.PaintGlyph, 0, "b")}
+
+ colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
+
+ assert not colorGlyphsV0
+ assert colorGlyphsV1 == colorGlyphs
+
+ colorGlyphs = {
+ "a": [("b", 0)],
+ "c": [
+ ("d", 1),
+ (
+ "e",
+ {
+ "format": 3,
+ "colorLine": {"stops": [(0.0, 2), (1.0, 3)]},
+ "p0": (0, 0),
+ "p1": (10, 10),
+ },
+ ),
+ ],
+ }
+
+ colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
+
+ assert colorGlyphsV0 == {"a": [("b", 0)]}
+ assert "a" not in colorGlyphsV1
+ assert "c" in colorGlyphsV1
+ assert len(colorGlyphsV1["c"]) == 2
+
+
+def assertIsColrV1(colr):
+ assert colr.version == 1
+ assert not hasattr(colr, "ColorLayers")
+ assert hasattr(colr, "table")
+ assert isinstance(colr.table, ot.COLR)
+
+
+def assertNoV0Content(colr):
+ assert colr.table.BaseGlyphRecordCount == 0
+ assert colr.table.BaseGlyphRecordArray is None
+ assert colr.table.LayerRecordCount == 0
+ assert colr.table.LayerRecordArray is None
+
+
+def test_build_layerv1list_empty():
+ # Nobody uses PaintColrLayers, no layerlist
+ colr = builder.buildCOLR(
+ {
+ # BaseGlyph, tuple form
+ "a": (
+ int(ot.PaintFormat.PaintGlyph),
+ (2, (2, 0.8)),
+ "b",
+ ),
+ # BaseGlyph, map form
+ "b": {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintLinearGradient),
+ "ColorLine": {
+ "ColorStop": [(0.0, 2), (1.0, 3)],
+ "Extend": "reflect",
+ },
+ "x0": 1,
+ "y0": 2,
+ "x1": 3,
+ "y1": 4,
+ "x2": 2,
+ "y2": 2,
+ },
+ "Glyph": "bb",
+ },
+ },
+ version=1,
+ )
+
+ assertIsColrV1(colr)
+ assertNoV0Content(colr)
+
+ # 2 v1 glyphs, none in LayerV1List
+ assert colr.table.BaseGlyphV1List.BaseGlyphCount == 2
+ assert len(colr.table.BaseGlyphV1List.BaseGlyphV1Record) == 2
+ assert colr.table.LayerV1List.LayerCount == 0
+ assert len(colr.table.LayerV1List.Paint) == 0
+
+
+def _paint_names(paints) -> List[str]:
+ # prints a predictable string from a paint list to enable
+ # semi-readable assertions on a LayerV1List order.
+ result = []
+ for paint in paints:
+ if paint.Format == int(ot.PaintFormat.PaintGlyph):
+ result.append(paint.Glyph)
+ elif paint.Format == int(ot.PaintFormat.PaintColrLayers):
+ result.append(
+ f"Layers[{paint.FirstLayerIndex}:{paint.FirstLayerIndex+paint.NumLayers}]"
+ )
+ return result
+
+
+def test_build_layerv1list_simple():
+ # Two colr glyphs, each with two layers the first of which is common
+ # All layers use the same solid paint
+ solid_paint = {"Format": 2, "Color": {"PaletteIndex": 2, "Alpha": 0.8}}
+ backdrop = {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": solid_paint,
+ "Glyph": "back",
+ }
+ a_foreground = {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": solid_paint,
+ "Glyph": "a_fore",
+ }
+ b_foreground = {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": solid_paint,
+ "Glyph": "b_fore",
+ }
+
+ # list => PaintColrLayers, contents should land in LayerV1List
+ colr = builder.buildCOLR(
+ {
+ "a": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ backdrop,
+ a_foreground,
+ ],
+ ),
+ "b": {
+ "Format": ot.PaintFormat.PaintColrLayers,
+ "Layers": [
+ backdrop,
+ b_foreground,
+ ],
+ },
+ },
+ version=1,
+ )
+
+ assertIsColrV1(colr)
+ assertNoV0Content(colr)
+
+ # 2 v1 glyphs, 4 paints in LayerV1List
+ # A single shared backdrop isn't worth accessing by slice
+ assert colr.table.BaseGlyphV1List.BaseGlyphCount == 2
+ assert len(colr.table.BaseGlyphV1List.BaseGlyphV1Record) == 2
+ assert colr.table.LayerV1List.LayerCount == 4
+ assert _paint_names(colr.table.LayerV1List.Paint) == [
+ "back",
+ "a_fore",
+ "back",
+ "b_fore",
+ ]
+
+
+def test_build_layerv1list_with_sharing():
+ # Three colr glyphs, each with two layers in common
+ solid_paint = {"Format": 2, "Color": (2, 0.8)}
+ backdrop = [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": solid_paint,
+ "Glyph": "back1",
+ },
+ {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": solid_paint,
+ "Glyph": "back2",
+ },
+ ]
+ a_foreground = {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": solid_paint,
+ "Glyph": "a_fore",
+ }
+ b_background = {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": solid_paint,
+ "Glyph": "b_back",
+ }
+ b_foreground = {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": solid_paint,
+ "Glyph": "b_fore",
+ }
+ c_background = {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": solid_paint,
+ "Glyph": "c_back",
+ }
+
+ # list => PaintColrLayers, which means contents should be in LayerV1List
+ colr = builder.buildCOLR(
+ {
+ "a": (ot.PaintFormat.PaintColrLayers, backdrop + [a_foreground]),
+ "b": (
+ ot.PaintFormat.PaintColrLayers,
+ [b_background] + backdrop + [b_foreground],
+ ),
+ "c": (ot.PaintFormat.PaintColrLayers, [c_background] + backdrop),
+ },
+ version=1,
+ )
+
+ assertIsColrV1(colr)
+ assertNoV0Content(colr)
+
+ # 2 v1 glyphs, 4 paints in LayerV1List
+ # A single shared backdrop isn't worth accessing by slice
+ baseGlyphs = colr.table.BaseGlyphV1List.BaseGlyphV1Record
+ assert colr.table.BaseGlyphV1List.BaseGlyphCount == 3
+ assert len(baseGlyphs) == 3
+ assert _paint_names([b.Paint for b in baseGlyphs]) == [
+ "Layers[0:3]",
+ "Layers[3:6]",
+ "Layers[6:8]",
+ ]
+ assert _paint_names(colr.table.LayerV1List.Paint) == [
+ "back1",
+ "back2",
+ "a_fore",
+ "b_back",
+ "Layers[0:2]",
+ "b_fore",
+ "c_back",
+ "Layers[0:2]",
+ ]
+ assert colr.table.LayerV1List.LayerCount == 8
+
+
+def test_build_layerv1list_with_overlaps():
+ paints = [
+ {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": {
+ "Format": ot.PaintFormat.PaintSolid,
+ "Color": {"PaletteIndex": 2, "Alpha": 0.8},
+ },
+ "Glyph": c,
+ }
+ for c in "abcdefghi"
+ ]
+
+ # list => PaintColrLayers, which means contents should be in LayerV1List
+ colr = builder.buildCOLR(
+ {
+ "a": (ot.PaintFormat.PaintColrLayers, paints[0:4]),
+ "b": (ot.PaintFormat.PaintColrLayers, paints[0:6]),
+ "c": (ot.PaintFormat.PaintColrLayers, paints[2:8]),
+ },
+ version=1,
+ )
+
+ assertIsColrV1(colr)
+ assertNoV0Content(colr)
+
+ baseGlyphs = colr.table.BaseGlyphV1List.BaseGlyphV1Record
+ # assert colr.table.BaseGlyphV1List.BaseGlyphCount == 2
+
+ assert _paint_names(colr.table.LayerV1List.Paint) == [
+ "a",
+ "b",
+ "c",
+ "d",
+ "Layers[0:4]",
+ "e",
+ "f",
+ "Layers[2:4]",
+ "Layers[5:7]",
+ "g",
+ "h",
+ ]
+ assert _paint_names([b.Paint for b in baseGlyphs]) == [
+ "Layers[0:4]",
+ "Layers[4:7]",
+ "Layers[7:11]",
+ ]
+ assert colr.table.LayerV1List.LayerCount == 11
+
+
+def test_explicit_version_1():
+ colr = builder.buildCOLR(
+ {
+ "a": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, 0), "b"),
+ (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, 1), "c"),
+ ],
+ )
+ },
+ version=1,
+ )
+ assert colr.version == 1
+ assert not hasattr(colr, "ColorLayers")
+ assert hasattr(colr, "table")
+ assert isinstance(colr.table, ot.COLR)
+ assert colr.table.VarStore is None
+
+
+class BuildCOLRTest(object):
+ def test_automatic_version_all_solid_color_glyphs(self):
+ colr = builder.buildCOLR({"a": [("b", 0), ("c", 1)]})
+ assert colr.version == 0
+ assert hasattr(colr, "ColorLayers")
+ assert colr.ColorLayers["a"][0].name == "b"
+ assert colr.ColorLayers["a"][1].name == "c"
+
+ def test_automatic_version_no_solid_color_glyphs(self):
+ colr = builder.buildCOLR(
+ {
+ "a": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ (
+ ot.PaintFormat.PaintGlyph,
+ {
+ "Format": int(ot.PaintFormat.PaintRadialGradient),
+ "ColorLine": {
+ "ColorStop": [(0.0, 0), (1.0, 1)],
+ "Extend": "repeat",
+ },
+ "x0": 1,
+ "y0": 0,
+ "x1": 10,
+ "y1": 0,
+ "r0": 4,
+ "r1": 2,
+ },
+ "b",
+ ),
+ (
+ ot.PaintFormat.PaintGlyph,
+ {"Format": 2, "Color": {"PaletteIndex": 2, "Alpha": 0.8}},
+ "c",
+ ),
+ ],
+ ),
+ "d": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Glyph": "e",
+ "Paint": {
+ "Format": ot.PaintFormat.PaintLinearGradient,
+ "ColorLine": {
+ "ColorStop": [(0.0, 2), (1.0, 3)],
+ "Extend": "reflect",
+ },
+ "x0": 1,
+ "y0": 2,
+ "x1": 3,
+ "y1": 4,
+ "x2": 2,
+ "y2": 2,
+ },
+ }
+ ],
+ ),
+ }
+ )
+ assertIsColrV1(colr)
+ assert colr.table.BaseGlyphRecordCount == 0
+ assert colr.table.BaseGlyphRecordArray is None
+ assert colr.table.LayerRecordCount == 0
+ assert colr.table.LayerRecordArray is None
+
+ def test_automatic_version_mixed_solid_and_gradient_glyphs(self):
+ colr = builder.buildCOLR(
+ {
+ "a": [("b", 0), ("c", 1)],
+ "d": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ (
+ ot.PaintFormat.PaintGlyph,
+ {
+ "Format": ot.PaintFormat.PaintLinearGradient,
+ "ColorLine": {"ColorStop": [(0.0, 2), (1.0, 3)]},
+ "x0": 1,
+ "y0": 2,
+ "x1": 3,
+ "y1": 4,
+ "x2": 2,
+ "y2": 2,
+ },
+ "e",
+ ),
+ (
+ ot.PaintFormat.PaintGlyph,
+ (ot.PaintFormat.PaintSolid, (2, 0.8)),
+ "f",
+ ),
+ ],
+ ),
+ }
+ )
+ assertIsColrV1(colr)
+ assert colr.table.VarStore is None
+
+ assert colr.table.BaseGlyphRecordCount == 1
+ assert isinstance(colr.table.BaseGlyphRecordArray, ot.BaseGlyphRecordArray)
+ assert colr.table.LayerRecordCount == 2
+ assert isinstance(colr.table.LayerRecordArray, ot.LayerRecordArray)
+
+ assert isinstance(colr.table.BaseGlyphV1List, ot.BaseGlyphV1List)
+ assert colr.table.BaseGlyphV1List.BaseGlyphCount == 1
+ assert isinstance(
+ colr.table.BaseGlyphV1List.BaseGlyphV1Record[0], ot.BaseGlyphV1Record
+ )
+ assert colr.table.BaseGlyphV1List.BaseGlyphV1Record[0].BaseGlyph == "d"
+ assert isinstance(colr.table.LayerV1List, ot.LayerV1List)
+ assert colr.table.LayerV1List.Paint[0].Glyph == "e"
+
+ def test_explicit_version_0(self):
+ colr = builder.buildCOLR({"a": [("b", 0), ("c", 1)]}, version=0)
+ assert colr.version == 0
+ assert hasattr(colr, "ColorLayers")
+
+ def test_explicit_version_1(self):
+ colr = builder.buildCOLR(
+ {
+ "a": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ (
+ ot.PaintFormat.PaintGlyph,
+ (ot.PaintFormat.PaintSolid, 0),
+ "b",
+ ),
+ (
+ ot.PaintFormat.PaintGlyph,
+ (ot.PaintFormat.PaintSolid, 1),
+ "c",
+ ),
+ ],
+ )
+ },
+ version=1,
+ )
+ assert colr.version == 1
+ assert not hasattr(colr, "ColorLayers")
+ assert hasattr(colr, "table")
+ assert isinstance(colr.table, ot.COLR)
+ assert colr.table.VarStore is None
+
+ def test_paint_one_colr_layers(self):
+ # A set of one layers should flip to just that layer
+ colr = builder.buildCOLR(
+ {
+ "a": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ (
+ ot.PaintFormat.PaintGlyph,
+ (ot.PaintFormat.PaintSolid, 0),
+ "b",
+ ),
+ ],
+ )
+ },
+ )
+
+ assert len(colr.table.LayerV1List.Paint) == 0, "PaintColrLayers should be gone"
+ assert colr.table.BaseGlyphV1List.BaseGlyphCount == 1
+ paint = colr.table.BaseGlyphV1List.BaseGlyphV1Record[0].Paint
+ assert paint.Format == ot.PaintFormat.PaintGlyph
+ assert paint.Paint.Format == ot.PaintFormat.PaintSolid
+
+
+class TrickyRadialGradientTest:
+ @staticmethod
+ def circle_inside_circle(c0, r0, c1, r1, rounded=False):
+ if rounded:
+ return Circle(c0, r0).round().inside(Circle(c1, r1).round())
+ else:
+ return Circle(c0, r0).inside(Circle(c1, r1))
+
+ def round_start_circle(self, c0, r0, c1, r1, inside=True):
+ assert self.circle_inside_circle(c0, r0, c1, r1) is inside
+ assert self.circle_inside_circle(c0, r0, c1, r1, rounded=True) is not inside
+ r = round_start_circle_stable_containment(c0, r0, c1, r1)
+ assert (
+ self.circle_inside_circle(r.centre, r.radius, c1, r1, rounded=True)
+ is inside
+ )
+ return r.centre, r.radius
+
+ def test_noto_emoji_mosquito_u1f99f(self):
+ # https://github.com/googlefonts/picosvg/issues/158
+ c0 = (385.23508, 70.56727999999998)
+ r0 = 0
+ c1 = (642.99108, 104.70327999999995)
+ r1 = 260.0072
+ assert self.round_start_circle(c0, r0, c1, r1, inside=True) == ((386, 71), 0)
+
+ @pytest.mark.parametrize(
+ "c0, r0, c1, r1, inside, expected",
+ [
+ # inside before round, outside after round
+ ((1.4, 0), 0, (2.6, 0), 1.3, True, ((2, 0), 0)),
+ ((1, 0), 0.6, (2.8, 0), 2.45, True, ((2, 0), 1)),
+ ((6.49, 6.49), 0, (0.49, 0.49), 8.49, True, ((5, 5), 0)),
+ # outside before round, inside after round
+ ((0, 0), 0, (2, 0), 1.5, False, ((-1, 0), 0)),
+ ((0, -0.5), 0, (0, -2.5), 1.5, False, ((0, 1), 0)),
+ # the following ones require two nudges to round correctly
+ ((0.5, 0), 0, (9.4, 0), 8.8, False, ((-1, 0), 0)),
+ ((1.5, 1.5), 0, (0.49, 0.49), 1.49, True, ((0, 0), 0)),
+ # limit case when circle almost exactly overlap
+ ((0.5000001, 0), 0.5000001, (0.499999, 0), 0.4999999, True, ((0, 0), 0)),
+ # concentrical circles, r0 > r1
+ ((0, 0), 1.49, (0, 0), 1, False, ((0, 0), 2)),
+ ],
+ )
+ def test_nudge_start_circle_position(self, c0, r0, c1, r1, inside, expected):
+ assert self.round_start_circle(c0, r0, c1, r1, inside) == expected
+
+
+@pytest.mark.parametrize(
+ "lst, n, expected",
+ [
+ ([0], 2, [0]),
+ ([0, 1], 2, [0, 1]),
+ ([0, 1, 2], 2, [[0, 1], 2]),
+ ([0, 1, 2], 3, [0, 1, 2]),
+ ([0, 1, 2, 3], 2, [[0, 1], [2, 3]]),
+ ([0, 1, 2, 3], 3, [[0, 1, 2], 3]),
+ ([0, 1, 2, 3, 4], 3, [[0, 1, 2], 3, 4]),
+ ([0, 1, 2, 3, 4, 5], 3, [[0, 1, 2], [3, 4, 5]]),
+ (list(range(7)), 3, [[0, 1, 2], [3, 4, 5], 6]),
+ (list(range(8)), 3, [[0, 1, 2], [3, 4, 5], [6, 7]]),
+ (list(range(9)), 3, [[0, 1, 2], [3, 4, 5], [6, 7, 8]]),
+ (list(range(10)), 3, [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], 9]),
+ (list(range(11)), 3, [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], 9, 10]),
+ (list(range(12)), 3, [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [9, 10, 11]]),
+ (list(range(13)), 3, [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [9, 10, 11], 12]),
+ (
+ list(range(14)),
+ 3,
+ [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [[9, 10, 11], 12, 13]],
+ ),
+ (
+ list(range(15)),
+ 3,
+ [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [9, 10, 11], [12, 13, 14]],
+ ),
+ (
+ list(range(16)),
+ 3,
+ [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [[9, 10, 11], [12, 13, 14], 15]],
+ ),
+ (
+ list(range(23)),
+ 3,
+ [
+ [[0, 1, 2], [3, 4, 5], [6, 7, 8]],
+ [[9, 10, 11], [12, 13, 14], [15, 16, 17]],
+ [[18, 19, 20], 21, 22],
+ ],
+ ),
+ (
+ list(range(27)),
+ 3,
+ [
+ [[0, 1, 2], [3, 4, 5], [6, 7, 8]],
+ [[9, 10, 11], [12, 13, 14], [15, 16, 17]],
+ [[18, 19, 20], [21, 22, 23], [24, 25, 26]],
+ ],
+ ),
+ (
+ list(range(28)),
+ 3,
+ [
+ [
+ [[0, 1, 2], [3, 4, 5], [6, 7, 8]],
+ [[9, 10, 11], [12, 13, 14], [15, 16, 17]],
+ [[18, 19, 20], [21, 22, 23], [24, 25, 26]],
+ ],
+ 27,
+ ],
+ ),
+ (list(range(257)), 256, [list(range(256)), 256]),
+ (list(range(258)), 256, [list(range(256)), 256, 257]),
+ (list(range(512)), 256, [list(range(256)), list(range(256, 512))]),
+ (list(range(512 + 1)), 256, [list(range(256)), list(range(256, 512)), 512]),
+ (
+ list(range(256 ** 2)),
+ 256,
+ [list(range(k * 256, k * 256 + 256)) for k in range(256)],
+ ),
+ ],
+)
+def test_build_n_ary_tree(lst, n, expected):
+ assert _build_n_ary_tree(lst, n) == expected
diff --git a/Tests/colorLib/table_builder_test.py b/Tests/colorLib/table_builder_test.py
new file mode 100644
index 00000000..d0a76f5a
--- /dev/null
+++ b/Tests/colorLib/table_builder_test.py
@@ -0,0 +1,15 @@
+from fontTools.ttLib.tables import otTables # trigger setup to occur
+from fontTools.ttLib.tables.otConverters import UShort
+from fontTools.colorLib.table_builder import TableBuilder
+import pytest
+
+
+class WriteMe:
+ value = None
+
+
+def test_intValue_otRound():
+ dest = WriteMe()
+ converter = UShort("value", None, None)
+ TableBuilder()._convert(dest, "value", converter, 85.6)
+ assert dest.value == 86, "Should have used otRound"
diff --git a/Tests/colorLib/unbuilder_test.py b/Tests/colorLib/unbuilder_test.py
new file mode 100644
index 00000000..81169e03
--- /dev/null
+++ b/Tests/colorLib/unbuilder_test.py
@@ -0,0 +1,210 @@
+from fontTools.ttLib.tables import otTables as ot
+from fontTools.colorLib.builder import buildColrV1
+from fontTools.colorLib.unbuilder import unbuildColrV1
+import pytest
+
+
+TEST_COLOR_GLYPHS = {
+ "glyph00010": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "Color": {"PaletteIndex": 2, "Alpha": 0.5},
+ },
+ "Glyph": "glyph00011",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintVarLinearGradient),
+ "ColorLine": {
+ "Extend": "repeat",
+ "ColorStop": [
+ {
+ "StopOffset": (0.0, 0),
+ "Color": {"PaletteIndex": 3, "Alpha": (1.0, 0)},
+ },
+ {
+ "StopOffset": (0.5, 0),
+ "Color": {"PaletteIndex": 4, "Alpha": (1.0, 0)},
+ },
+ {
+ "StopOffset": (1.0, 0),
+ "Color": {"PaletteIndex": 5, "Alpha": (1.0, 0)},
+ },
+ ],
+ },
+ "x0": (1, 0),
+ "y0": (2, 0),
+ "x1": (-3, 0),
+ "y1": (-4, 0),
+ "x2": (5, 0),
+ "y2": (6, 0),
+ },
+ "Glyph": "glyph00012",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintVarTransform),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintRadialGradient),
+ "ColorLine": {
+ "Extend": "pad",
+ "ColorStop": [
+ {
+ "StopOffset": 0,
+ "Color": {"PaletteIndex": 6, "Alpha": 1.0},
+ },
+ {
+ "StopOffset": 1.0,
+ "Color": {"PaletteIndex": 7, "Alpha": 0.4},
+ },
+ ],
+ },
+ "x0": 7,
+ "y0": 8,
+ "r0": 9,
+ "x1": 10,
+ "y1": 11,
+ "r1": 12,
+ },
+ "Transform": {
+ "xx": (-13.0, 0),
+ "yx": (14.0, 0),
+ "xy": (15.0, 0),
+ "yy": (-17.0, 0),
+ "dx": (18.0, 0),
+ "dy": (19.0, 0),
+ },
+ },
+ "Glyph": "glyph00013",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintVarTranslate),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintRotate),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintVarSkew),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "Color": {"PaletteIndex": 2, "Alpha": 0.5},
+ },
+ "Glyph": "glyph00011",
+ },
+ "xSkewAngle": (-11.0, 0),
+ "ySkewAngle": (5.0, 0),
+ "centerX": (253.0, 0),
+ "centerY": (254.0, 0),
+ },
+ "angle": 45.0,
+ "centerX": 255.0,
+ "centerY": 256.0,
+ },
+ "dx": (257.0, 0),
+ "dy": (258.0, 0),
+ },
+ ],
+ },
+ "glyph00014": {
+ "Format": int(ot.PaintFormat.PaintComposite),
+ "SourcePaint": {
+ "Format": int(ot.PaintFormat.PaintColrGlyph),
+ "Glyph": "glyph00010",
+ },
+ "CompositeMode": "src_over",
+ "BackdropPaint": {
+ "Format": int(ot.PaintFormat.PaintTransform),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintColrGlyph),
+ "Glyph": "glyph00010",
+ },
+ "Transform": {
+ "xx": 1.0,
+ "yx": 0.0,
+ "xy": 0.0,
+ "yy": 1.0,
+ "dx": 300.0,
+ "dy": 0.0,
+ },
+ },
+ },
+ "glyph00015": {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSweepGradient),
+ "ColorLine": {
+ "Extend": "pad",
+ "ColorStop": [
+ {
+ "StopOffset": 0.0,
+ "Color": {"PaletteIndex": 3, "Alpha": 1.0},
+ },
+ {
+ "StopOffset": 1.0,
+ "Color": {"PaletteIndex": 5, "Alpha": 1.0},
+ },
+ ],
+ },
+ "centerX": 259,
+ "centerY": 300,
+ "startAngle": 45.0,
+ "endAngle": 135.0,
+ },
+ "Glyph": "glyph00011",
+ },
+ "glyph00016": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintVarSolid),
+ "Color": {"PaletteIndex": 2, "Alpha": (0.5, 0)},
+ },
+ "Glyph": "glyph00011",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintVarLinearGradient),
+ "ColorLine": {
+ "Extend": "repeat",
+ "ColorStop": [
+ {
+ "StopOffset": (0.0, 0),
+ "Color": {"PaletteIndex": 3, "Alpha": (1.0, 0)},
+ },
+ {
+ "StopOffset": (0.5, 0),
+ "Color": {"PaletteIndex": 4, "Alpha": (1.0, 0)},
+ },
+ {
+ "StopOffset": (1.0, 0),
+ "Color": {"PaletteIndex": 5, "Alpha": (1.0, 0)},
+ },
+ ],
+ },
+ "x0": (1, 0),
+ "y0": (2, 0),
+ "x1": (-3, 0),
+ "y1": (-4, 0),
+ "x2": (5, 0),
+ "y2": (6, 0),
+ },
+ "Glyph": "glyph00012",
+ },
+ ],
+ },
+}
+
+
+def test_unbuildColrV1():
+ layersV1, baseGlyphsV1 = buildColrV1(TEST_COLOR_GLYPHS)
+ colorGlyphs = unbuildColrV1(layersV1, baseGlyphsV1)
+ assert colorGlyphs == TEST_COLOR_GLYPHS
diff --git a/Tests/cu2qu/cli_test.py b/Tests/cu2qu/cli_test.py
new file mode 100644
index 00000000..f6798a63
--- /dev/null
+++ b/Tests/cu2qu/cli_test.py
@@ -0,0 +1,86 @@
+import os
+
+import pytest
+import py
+
+ufoLib2 = pytest.importorskip("ufoLib2")
+
+from fontTools.cu2qu.ufo import CURVE_TYPE_LIB_KEY
+from fontTools.cu2qu.cli import main
+
+
+DATADIR = os.path.join(os.path.dirname(__file__), 'data')
+
+TEST_UFOS = [
+ py.path.local(DATADIR).join("RobotoSubset-Regular.ufo"),
+ py.path.local(DATADIR).join("RobotoSubset-Bold.ufo"),
+]
+
+
+@pytest.fixture
+def test_paths(tmpdir):
+ result = []
+ for path in TEST_UFOS:
+ new_path = tmpdir / path.basename
+ path.copy(new_path)
+ result.append(new_path)
+ return result
+
+
+class MainTest(object):
+
+ @staticmethod
+ def run_main(*args):
+ main([str(p) for p in args if p])
+
+ def test_single_input_no_output(self, test_paths):
+ ufo_path = test_paths[0]
+
+ self.run_main(ufo_path)
+
+ font = ufoLib2.Font.open(ufo_path)
+ assert font.lib[CURVE_TYPE_LIB_KEY] == "quadratic"
+
+ def test_single_input_output_file(self, tmpdir):
+ input_path = TEST_UFOS[0]
+ output_path = tmpdir / input_path.basename
+ self.run_main('-o', output_path, input_path)
+
+ assert output_path.check(dir=1)
+
+ def test_multiple_inputs_output_dir(self, tmpdir):
+ output_dir = tmpdir / "output_dir"
+ self.run_main('-d', output_dir, *TEST_UFOS)
+
+ assert output_dir.check(dir=1)
+ outputs = set(p.basename for p in output_dir.listdir())
+ assert "RobotoSubset-Regular.ufo" in outputs
+ assert "RobotoSubset-Bold.ufo" in outputs
+
+ def test_interpolatable_inplace(self, test_paths):
+ self.run_main('-i', *test_paths)
+ self.run_main('-i', *test_paths) # idempotent
+
+ @pytest.mark.parametrize(
+ "mode", ["", "-i"], ids=["normal", "interpolatable"])
+ def test_copytree(self, mode, tmpdir):
+ output_dir = tmpdir / "output_dir"
+ self.run_main(mode, '-d', output_dir, *TEST_UFOS)
+
+ output_dir_2 = tmpdir / "output_dir_2"
+ # no conversion when curves are already quadratic, just copy
+ self.run_main(mode, '-d', output_dir_2, *output_dir.listdir())
+ # running again overwrites existing with the copy
+ self.run_main(mode, '-d', output_dir_2, *output_dir.listdir())
+
+ def test_multiprocessing(self, tmpdir, test_paths):
+ self.run_main(*(test_paths + ["-j"]))
+
+ def test_keep_direction(self, test_paths):
+ self.run_main('--keep-direction', *test_paths)
+
+ def test_conversion_error(self, test_paths):
+ self.run_main('--conversion-error', 0.002, *test_paths)
+
+ def test_conversion_error_short(self, test_paths):
+ self.run_main('-e', 0.003, test_paths[0])
diff --git a/Tests/cu2qu/cu2qu_test.py b/Tests/cu2qu/cu2qu_test.py
new file mode 100644
index 00000000..456d2103
--- /dev/null
+++ b/Tests/cu2qu/cu2qu_test.py
@@ -0,0 +1,178 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import collections
+import math
+import unittest
+import os
+import json
+
+from fontTools.cu2qu import curve_to_quadratic, curves_to_quadratic
+
+
+DATADIR = os.path.join(os.path.dirname(__file__), 'data')
+
+MAX_ERR = 5
+
+
+class CurveToQuadraticTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ """Do the curve conversion ahead of time, and run tests on results."""
+ with open(os.path.join(DATADIR, "curves.json"), "r") as fp:
+ curves = json.load(fp)
+
+ cls.single_splines = [
+ curve_to_quadratic(c, MAX_ERR) for c in curves]
+ cls.single_errors = [
+ cls.curve_spline_dist(c, s)
+ for c, s in zip(curves, cls.single_splines)]
+
+ curve_groups = [curves[i:i + 3] for i in range(0, 300, 3)]
+ cls.compat_splines = [
+ curves_to_quadratic(c, [MAX_ERR] * 3) for c in curve_groups]
+ cls.compat_errors = [
+ [cls.curve_spline_dist(c, s) for c, s in zip(curve_group, splines)]
+ for curve_group, splines in zip(curve_groups, cls.compat_splines)]
+
+ cls.results = []
+
+ @classmethod
+ def tearDownClass(cls):
+ """Print stats from conversion, as determined during tests."""
+
+ for tag, results in cls.results:
+ print('\n%s\n%s' % (
+ tag, '\n'.join(
+ '%s: %s (%d)' % (k, '#' * (v // 10 + 1), v)
+ for k, v in sorted(results.items()))))
+
+ def test_results_unchanged(self):
+ """Tests that the results of conversion haven't changed since the time
+ of this test's writing. Useful as a quick check whenever one modifies
+ the conversion algorithm.
+ """
+
+ expected = {
+ 2: 6,
+ 3: 26,
+ 4: 82,
+ 5: 232,
+ 6: 360,
+ 7: 266,
+ 8: 28}
+
+ results = collections.defaultdict(int)
+ for spline in self.single_splines:
+ n = len(spline) - 2
+ results[n] += 1
+ self.assertEqual(results, expected)
+ self.results.append(('single spline lengths', results))
+
+ def test_results_unchanged_multiple(self):
+ """Test that conversion results are unchanged for multiple curves."""
+
+ expected = {
+ 5: 11,
+ 6: 35,
+ 7: 49,
+ 8: 5}
+
+ results = collections.defaultdict(int)
+ for splines in self.compat_splines:
+ n = len(splines[0]) - 2
+ for spline in splines[1:]:
+ self.assertEqual(len(spline) - 2, n,
+ 'Got incompatible conversion results')
+ results[n] += 1
+ self.assertEqual(results, expected)
+ self.results.append(('compatible spline lengths', results))
+
+ def test_does_not_exceed_tolerance(self):
+ """Test that conversion results do not exceed given error tolerance."""
+
+ results = collections.defaultdict(int)
+ for error in self.single_errors:
+ results[round(error, 1)] += 1
+ self.assertLessEqual(error, MAX_ERR)
+ self.results.append(('single errors', results))
+
+ def test_does_not_exceed_tolerance_multiple(self):
+ """Test that error tolerance isn't exceeded for multiple curves."""
+
+ results = collections.defaultdict(int)
+ for errors in self.compat_errors:
+ for error in errors:
+ results[round(error, 1)] += 1
+ self.assertLessEqual(error, MAX_ERR)
+ self.results.append(('compatible errors', results))
+
+ @classmethod
+ def curve_spline_dist(cls, bezier, spline, total_steps=20):
+ """Max distance between a bezier and quadratic spline at sampled points."""
+
+ error = 0
+ n = len(spline) - 2
+ steps = total_steps // n
+ for i in range(0, n - 1):
+ p1 = spline[0] if i == 0 else p3
+ p2 = spline[i + 1]
+ if i < n - 1:
+ p3 = cls.lerp(spline[i + 1], spline[i + 2], 0.5)
+ else:
+ p3 = spline[n + 2]
+ segment = p1, p2, p3
+ for j in range(steps):
+ error = max(error, cls.dist(
+ cls.cubic_bezier_at(bezier, (j / steps + i) / n),
+ cls.quadratic_bezier_at(segment, j / steps)))
+ return error
+
+ @classmethod
+ def lerp(cls, p1, p2, t):
+ (x1, y1), (x2, y2) = p1, p2
+ return x1 + (x2 - x1) * t, y1 + (y2 - y1) * t
+
+ @classmethod
+ def dist(cls, p1, p2):
+ (x1, y1), (x2, y2) = p1, p2
+ return math.hypot(x1 - x2, y1 - y2)
+
+ @classmethod
+ def quadratic_bezier_at(cls, b, t):
+ (x1, y1), (x2, y2), (x3, y3) = b
+ _t = 1 - t
+ t2 = t * t
+ _t2 = _t * _t
+ _2_t_t = 2 * t * _t
+ return (_t2 * x1 + _2_t_t * x2 + t2 * x3,
+ _t2 * y1 + _2_t_t * y2 + t2 * y3)
+
+ @classmethod
+ def cubic_bezier_at(cls, b, t):
+ (x1, y1), (x2, y2), (x3, y3), (x4, y4) = b
+ _t = 1 - t
+ t2 = t * t
+ _t2 = _t * _t
+ t3 = t * t2
+ _t3 = _t * _t2
+ _3_t2_t = 3 * t2 * _t
+ _3_t_t2 = 3 * t * _t2
+ return (_t3 * x1 + _3_t_t2 * x2 + _3_t2_t * x3 + t3 * x4,
+ _t3 * y1 + _3_t_t2 * y2 + _3_t2_t * y3 + t3 * y4)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/fontinfo.plist b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/fontinfo.plist
new file mode 100644
index 00000000..21621a25
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/fontinfo.plist
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>ascender</key>
+ <integer>2146</integer>
+ <key>capHeight</key>
+ <integer>1456</integer>
+ <key>copyright</key>
+ <string>Copyright 2011 Google Inc. All Rights Reserved.</string>
+ <key>descender</key>
+ <integer>-555</integer>
+ <key>familyName</key>
+ <string>RobotoSubset</string>
+ <key>italicAngle</key>
+ <integer>0</integer>
+ <key>openTypeHeadCreated</key>
+ <string>2008/09/12 12:29:34</string>
+ <key>openTypeHeadFlags</key>
+ <array>
+ <integer>0</integer>
+ <integer>1</integer>
+ <integer>3</integer>
+ <integer>4</integer>
+ </array>
+ <key>openTypeHeadLowestRecPPEM</key>
+ <integer>9</integer>
+ <key>openTypeHheaAscender</key>
+ <integer>1900</integer>
+ <key>openTypeHheaDescender</key>
+ <integer>-500</integer>
+ <key>openTypeHheaLineGap</key>
+ <integer>0</integer>
+ <key>openTypeNameDescription</key>
+ <string></string>
+ <key>openTypeNameDesigner</key>
+ <string></string>
+ <key>openTypeNameDesignerURL</key>
+ <string></string>
+ <key>openTypeNameLicense</key>
+ <string></string>
+ <key>openTypeNameLicenseURL</key>
+ <string></string>
+ <key>openTypeNameManufacturer</key>
+ <string></string>
+ <key>openTypeNameManufacturerURL</key>
+ <string></string>
+ <key>openTypeNameSampleText</key>
+ <string></string>
+ <key>openTypeOS2CodePageRanges</key>
+ <array>
+ <integer>0</integer>
+ <integer>1</integer>
+ <integer>2</integer>
+ <integer>3</integer>
+ <integer>4</integer>
+ <integer>7</integer>
+ <integer>8</integer>
+ <integer>29</integer>
+ </array>
+ <key>openTypeOS2FamilyClass</key>
+ <array>
+ <integer>0</integer>
+ <integer>0</integer>
+ </array>
+ <key>openTypeOS2Panose</key>
+ <array>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ </array>
+ <key>openTypeOS2Selection</key>
+ <array>
+ </array>
+ <key>openTypeOS2StrikeoutPosition</key>
+ <integer>512</integer>
+ <key>openTypeOS2StrikeoutSize</key>
+ <integer>102</integer>
+ <key>openTypeOS2SubscriptXOffset</key>
+ <integer>0</integer>
+ <key>openTypeOS2SubscriptXSize</key>
+ <integer>1434</integer>
+ <key>openTypeOS2SubscriptYOffset</key>
+ <integer>287</integer>
+ <key>openTypeOS2SubscriptYSize</key>
+ <integer>1331</integer>
+ <key>openTypeOS2SuperscriptXOffset</key>
+ <integer>0</integer>
+ <key>openTypeOS2SuperscriptXSize</key>
+ <integer>1434</integer>
+ <key>openTypeOS2SuperscriptYOffset</key>
+ <integer>977</integer>
+ <key>openTypeOS2SuperscriptYSize</key>
+ <integer>1331</integer>
+ <key>openTypeOS2Type</key>
+ <array>
+ </array>
+ <key>openTypeOS2TypoAscender</key>
+ <integer>2146</integer>
+ <key>openTypeOS2TypoDescender</key>
+ <integer>-555</integer>
+ <key>openTypeOS2TypoLineGap</key>
+ <integer>0</integer>
+ <key>openTypeOS2UnicodeRanges</key>
+ <array>
+ <integer>0</integer>
+ <integer>1</integer>
+ <integer>2</integer>
+ <integer>3</integer>
+ <integer>4</integer>
+ <integer>5</integer>
+ <integer>6</integer>
+ <integer>7</integer>
+ <integer>9</integer>
+ <integer>11</integer>
+ <integer>29</integer>
+ <integer>30</integer>
+ <integer>31</integer>
+ <integer>32</integer>
+ <integer>33</integer>
+ <integer>34</integer>
+ <integer>35</integer>
+ <integer>36</integer>
+ <integer>37</integer>
+ <integer>38</integer>
+ <integer>40</integer>
+ <integer>45</integer>
+ <integer>60</integer>
+ <integer>62</integer>
+ <integer>64</integer>
+ <integer>69</integer>
+ </array>
+ <key>openTypeOS2VendorID</key>
+ <string>GOOG</string>
+ <key>openTypeOS2WeightClass</key>
+ <integer>700</integer>
+ <key>openTypeOS2WidthClass</key>
+ <integer>5</integer>
+ <key>openTypeOS2WinAscent</key>
+ <integer>2146</integer>
+ <key>openTypeOS2WinDescent</key>
+ <integer>555</integer>
+ <key>postscriptBlueFuzz</key>
+ <integer>1</integer>
+ <key>postscriptBlueScale</key>
+ <real>0.039625</real>
+ <key>postscriptBlueShift</key>
+ <integer>7</integer>
+ <key>postscriptBlueValues</key>
+ <array>
+ <integer>-20</integer>
+ <integer>0</integer>
+ <integer>1082</integer>
+ <integer>1102</integer>
+ <integer>1456</integer>
+ <integer>1476</integer>
+ </array>
+ <key>postscriptDefaultCharacter</key>
+ <string>space</string>
+ <key>postscriptForceBold</key>
+ <false/>
+ <key>postscriptIsFixedPitch</key>
+ <false/>
+ <key>postscriptOtherBlues</key>
+ <array>
+ <integer>-436</integer>
+ <integer>-416</integer>
+ </array>
+ <key>postscriptStemSnapH</key>
+ <array>
+ <integer>250</integer>
+ </array>
+ <key>postscriptStemSnapV</key>
+ <array>
+ <integer>316</integer>
+ </array>
+ <key>postscriptUnderlinePosition</key>
+ <integer>-150</integer>
+ <key>postscriptUnderlineThickness</key>
+ <integer>100</integer>
+ <key>postscriptUniqueID</key>
+ <integer>-1</integer>
+ <key>styleName</key>
+ <string>Bold</string>
+ <key>trademark</key>
+ <string></string>
+ <key>unitsPerEm</key>
+ <integer>2048</integer>
+ <key>versionMajor</key>
+ <integer>1</integer>
+ <key>versionMinor</key>
+ <integer>0</integer>
+ <key>xHeight</key>
+ <integer>1082</integer>
+ <key>year</key>
+ <integer>2017</integer>
+</dict>
+</plist>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/A_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/A_.glif
new file mode 100644
index 00000000..481f0061
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/A_.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="A" format="2">
+ <unicode hex="0041"/>
+ <advance width="1390"/>
+ <outline>
+ <contour>
+ <point x="727" y="1150" type="line"/>
+ <point x="764" y="1456" type="line"/>
+ <point x="537" y="1456" type="line"/>
+ <point x="0" y="0" type="line"/>
+ <point x="358" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1032" y="0" type="line"/>
+ <point x="1391" y="0" type="line"/>
+ <point x="851" y="1456" type="line"/>
+ <point x="621" y="1456" type="line"/>
+ <point x="662" y="1150" type="line"/>
+ </contour>
+ <contour>
+ <point x="1018" y="543" type="line"/>
+ <point x="262" y="543" type="line"/>
+ <point x="262" y="272" type="line"/>
+ <point x="1018" y="272" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/B_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/B_.glif
new file mode 100644
index 00000000..a4c9f254
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/B_.glif
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="B" format="2">
+ <unicode hex="0042"/>
+ <advance width="1317"/>
+ <outline>
+ <contour>
+ <point x="703" y="619" type="line"/>
+ <point x="797" y="710" type="line"/>
+ <point x="1092" y="713"/>
+ <point x="1199" y="875"/>
+ <point x="1199" y="1054" type="curve"/>
+ <point x="1199" y="1326"/>
+ <point x="988" y="1456"/>
+ <point x="636" y="1456" type="curve"/>
+ <point x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ <point x="452" y="1185" type="line"/>
+ <point x="636" y="1185" type="line"/>
+ <point x="795" y="1185"/>
+ <point x="864" y="1135"/>
+ <point x="864" y="1012" type="curve"/>
+ <point x="864" y="904"/>
+ <point x="797" y="849"/>
+ <point x="635" y="849" type="curve"/>
+ <point x="328" y="849" type="line"/>
+ <point x="330" y="619" type="line"/>
+ </contour>
+ <contour>
+ <point x="690" y="0" type="line"/>
+ <point x="1042" y="0"/>
+ <point x="1230" y="145"/>
+ <point x="1230" y="431" type="curve" smooth="yes"/>
+ <point x="1230" y="598"/>
+ <point x="1129" y="769"/>
+ <point x="846" y="757" type="curve"/>
+ <point x="768" y="849" type="line"/>
+ <point x="412" y="849" type="line"/>
+ <point x="410" y="619" type="line"/>
+ <point x="703" y="619" type="line" smooth="yes"/>
+ <point x="842" y="619"/>
+ <point x="896" y="548"/>
+ <point x="896" y="436" type="curve" smooth="yes"/>
+ <point x="896" y="344"/>
+ <point x="836" y="270"/>
+ <point x="690" y="270" type="curve" smooth="yes"/>
+ <point x="365" y="270" type="line"/>
+ <point x="245" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/C_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/C_.glif
new file mode 100644
index 00000000..1d8e1e13
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/C_.glif
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="C" format="2">
+ <unicode hex="0043"/>
+ <advance width="1343"/>
+ <outline>
+ <contour>
+ <point x="950" y="493" type="line"/>
+ <point x="942" y="329"/>
+ <point x="856" y="255"/>
+ <point x="687" y="255" type="curve" smooth="yes"/>
+ <point x="489" y="255"/>
+ <point x="415" y="379"/>
+ <point x="415" y="688" type="curve" smooth="yes"/>
+ <point x="415" y="769" type="line" smooth="yes"/>
+ <point x="415" y="1079"/>
+ <point x="503" y="1202"/>
+ <point x="685" y="1202" type="curve" smooth="yes"/>
+ <point x="875" y="1202"/>
+ <point x="944" y="1117"/>
+ <point x="952" y="953" type="curve"/>
+ <point x="1286" y="953" type="line"/>
+ <point x="1259" y="1255"/>
+ <point x="1060" y="1477"/>
+ <point x="685" y="1477" type="curve" smooth="yes"/>
+ <point x="316" y="1477"/>
+ <point x="75" y="1205"/>
+ <point x="75" y="767" type="curve"/>
+ <point x="75" y="688" type="line"/>
+ <point x="75" y="250"/>
+ <point x="303" y="-20"/>
+ <point x="687" y="-20" type="curve" smooth="yes"/>
+ <point x="1046" y="-20"/>
+ <point x="1268" y="188"/>
+ <point x="1284" y="493" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/D_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/D_.glif
new file mode 100644
index 00000000..1d82e15d
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/D_.glif
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="D" format="2">
+ <unicode hex="0044"/>
+ <advance width="1327"/>
+ <outline>
+ <contour>
+ <point x="581" y="0" type="line"/>
+ <point x="973" y="0"/>
+ <point x="1251" y="285"/>
+ <point x="1251" y="697" type="curve"/>
+ <point x="1251" y="758" type="line"/>
+ <point x="1251" y="1170"/>
+ <point x="973" y="1456"/>
+ <point x="579" y="1456" type="curve"/>
+ <point x="255" y="1456" type="line"/>
+ <point x="255" y="1185" type="line"/>
+ <point x="579" y="1185" type="line"/>
+ <point x="794" y="1185"/>
+ <point x="910" y="1039"/>
+ <point x="910" y="760" type="curve"/>
+ <point x="910" y="697" type="line" smooth="yes"/>
+ <point x="910" y="416"/>
+ <point x="793" y="270"/>
+ <point x="581" y="270" type="curve"/>
+ <point x="263" y="270" type="line"/>
+ <point x="261" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="452" y="1456" type="line"/>
+ <point x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/E_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/E_.glif
new file mode 100644
index 00000000..4d867ee1
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/E_.glif
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="E" format="2">
+ <unicode hex="0045"/>
+ <advance width="1148"/>
+ <outline>
+ <contour>
+ <point x="1111" y="270" type="line"/>
+ <point x="337" y="270" type="line"/>
+ <point x="337" y="0" type="line"/>
+ <point x="1111" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="452" y="1456" type="line"/>
+ <point x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1011" y="878" type="line"/>
+ <point x="337" y="878" type="line"/>
+ <point x="337" y="617" type="line"/>
+ <point x="1011" y="617" type="line"/>
+ </contour>
+ <contour>
+ <point x="1112" y="1456" type="line"/>
+ <point x="337" y="1456" type="line"/>
+ <point x="337" y="1185" type="line"/>
+ <point x="1112" y="1185" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/F_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/F_.glif
new file mode 100644
index 00000000..2e0d20c3
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/F_.glif
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="F" format="2">
+ <unicode hex="0046"/>
+ <advance width="1121"/>
+ <outline>
+ <contour>
+ <point x="452" y="1456" type="line"/>
+ <point x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1021" y="850" type="line"/>
+ <point x="359" y="850" type="line"/>
+ <point x="359" y="580" type="line"/>
+ <point x="1021" y="580" type="line"/>
+ </contour>
+ <contour>
+ <point x="1083" y="1456" type="line"/>
+ <point x="359" y="1456" type="line"/>
+ <point x="359" y="1185" type="line"/>
+ <point x="1083" y="1185" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/G_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/G_.glif
new file mode 100644
index 00000000..6e65a0c1
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/G_.glif
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="G" format="2">
+ <unicode hex="0047"/>
+ <advance width="1396"/>
+ <outline>
+ <contour>
+ <point x="1296" y="778" type="line"/>
+ <point x="708" y="778" type="line"/>
+ <point x="708" y="537" type="line"/>
+ <point x="961" y="537" type="line"/>
+ <point x="961" y="311" type="line"/>
+ <point x="931" y="284"/>
+ <point x="868" y="250"/>
+ <point x="746" y="250" type="curve" smooth="yes"/>
+ <point x="529" y="250"/>
+ <point x="426" y="396"/>
+ <point x="426" y="687" type="curve" smooth="yes"/>
+ <point x="426" y="770" type="line" smooth="yes"/>
+ <point x="426" y="1063"/>
+ <point x="535" y="1207"/>
+ <point x="715" y="1207" type="curve" smooth="yes"/>
+ <point x="883" y="1207"/>
+ <point x="951" y="1125"/>
+ <point x="972" y="981" type="curve"/>
+ <point x="1295" y="981" type="line"/>
+ <point x="1265" y="1272"/>
+ <point x="1098" y="1477"/>
+ <point x="704" y="1477" type="curve" smooth="yes"/>
+ <point x="340" y="1477"/>
+ <point x="86" y="1221"/>
+ <point x="86" y="768" type="curve" smooth="yes"/>
+ <point x="86" y="687" type="line" smooth="yes"/>
+ <point x="86" y="234"/>
+ <point x="340" y="-20"/>
+ <point x="724" y="-20" type="curve" smooth="yes"/>
+ <point x="1040" y="-20"/>
+ <point x="1223" y="98"/>
+ <point x="1296" y="180" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/H_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/H_.glif
new file mode 100644
index 00000000..4ae896da
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/H_.glif
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="H" format="2">
+ <unicode hex="0048"/>
+ <advance width="1442"/>
+ <outline>
+ <contour>
+ <point x="1094" y="878" type="line"/>
+ <point x="345" y="878" type="line"/>
+ <point x="345" y="608" type="line"/>
+ <point x="1094" y="608" type="line"/>
+ </contour>
+ <contour>
+ <point x="452" y="1456" type="line"/>
+ <point x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1324" y="1456" type="line"/>
+ <point x="990" y="1456" type="line"/>
+ <point x="990" y="0" type="line"/>
+ <point x="1324" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/I_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/I_.glif
new file mode 100644
index 00000000..c22d48af
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/I_.glif
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="I" format="2">
+ <unicode hex="0049"/>
+ <advance width="612"/>
+ <outline>
+ <contour>
+ <point x="473" y="1456" type="line"/>
+ <point x="139" y="1456" type="line"/>
+ <point x="139" y="0" type="line"/>
+ <point x="473" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/J_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/J_.glif
new file mode 100644
index 00000000..599404b8
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/J_.glif
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="J" format="2">
+ <unicode hex="004A"/>
+ <advance width="1149"/>
+ <outline>
+ <contour>
+ <point x="699" y="457" type="line"/>
+ <point x="699" y="327"/>
+ <point x="638" y="250"/>
+ <point x="536" y="250" type="curve" smooth="yes"/>
+ <point x="433" y="250"/>
+ <point x="374" y="292"/>
+ <point x="374" y="440" type="curve"/>
+ <point x="38" y="440" type="line"/>
+ <point x="38" y="124"/>
+ <point x="246" y="-20"/>
+ <point x="536" y="-20" type="curve" smooth="yes"/>
+ <point x="816" y="-20"/>
+ <point x="1033" y="165"/>
+ <point x="1033" y="457" type="curve"/>
+ <point x="1033" y="1456" type="line"/>
+ <point x="699" y="1456" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/K_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/K_.glif
new file mode 100644
index 00000000..15d08e12
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/K_.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="K" format="2">
+ <unicode hex="004B"/>
+ <advance width="1307"/>
+ <outline>
+ <contour>
+ <point x="452" y="1456" type="line"/>
+ <point x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1322" y="1456" type="line"/>
+ <point x="909" y="1456" type="line"/>
+ <point x="577" y="999" type="line"/>
+ <point x="361" y="679" type="line"/>
+ <point x="422" y="357" type="line"/>
+ <point x="754" y="718" type="line"/>
+ </contour>
+ <contour>
+ <point x="930" y="0" type="line"/>
+ <point x="1327" y="0" type="line"/>
+ <point x="794" y="857" type="line"/>
+ <point x="538" y="656" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/L_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/L_.glif
new file mode 100644
index 00000000..eb22f5f5
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/L_.glif
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="L" format="2">
+ <unicode hex="004C"/>
+ <advance width="1110"/>
+ <outline>
+ <contour>
+ <point x="1071" y="270" type="line"/>
+ <point x="337" y="270" type="line"/>
+ <point x="337" y="0" type="line"/>
+ <point x="1071" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="452" y="1456" type="line"/>
+ <point x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/M_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/M_.glif
new file mode 100644
index 00000000..9c1a8e5f
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/M_.glif
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="M" format="2">
+ <unicode hex="004D"/>
+ <advance width="1795"/>
+ <outline>
+ <contour>
+ <point x="279" y="1456" type="line"/>
+ <point x="784" y="0" type="line"/>
+ <point x="1008" y="0" type="line"/>
+ <point x="1513" y="1456" type="line"/>
+ <point x="1236" y="1456" type="line"/>
+ <point x="896" y="443" type="line"/>
+ <point x="556" y="1456" type="line"/>
+ </contour>
+ <contour>
+ <point x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ <point x="452" y="340" type="line"/>
+ <point x="400" y="1456" type="line"/>
+ </contour>
+ <contour>
+ <point x="1392" y="1456" type="line"/>
+ <point x="1340" y="340" type="line"/>
+ <point x="1340" y="0" type="line"/>
+ <point x="1676" y="0" type="line"/>
+ <point x="1676" y="1456" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/N_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/N_.glif
new file mode 100644
index 00000000..4b335f30
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/N_.glif
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="N" format="2">
+ <unicode hex="004E"/>
+ <advance width="1441"/>
+ <outline>
+ <contour>
+ <point x="1323" y="1456" type="line"/>
+ <point x="989" y="1456" type="line"/>
+ <point x="989" y="550" type="line"/>
+ <point x="452" y="1456" type="line"/>
+ <point x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ <point x="452" y="906" type="line"/>
+ <point x="989" y="0" type="line"/>
+ <point x="1323" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/O_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/O_.glif
new file mode 100644
index 00000000..8e6d1822
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/O_.glif
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="O" format="2">
+ <unicode hex="004F"/>
+ <advance width="1414"/>
+ <outline>
+ <contour>
+ <point x="1338" y="757" type="line"/>
+ <point x="1338" y="1202"/>
+ <point x="1076" y="1476"/>
+ <point x="706" y="1476" type="curve" smooth="yes"/>
+ <point x="334" y="1476"/>
+ <point x="75" y="1202"/>
+ <point x="75" y="757" type="curve"/>
+ <point x="75" y="698" type="line"/>
+ <point x="75" y="253"/>
+ <point x="336" y="-20"/>
+ <point x="708" y="-20" type="curve" smooth="yes"/>
+ <point x="1078" y="-20"/>
+ <point x="1338" y="253"/>
+ <point x="1338" y="698" type="curve"/>
+ </contour>
+ <contour>
+ <point x="998" y="698" type="line"/>
+ <point x="998" y="413"/>
+ <point x="894" y="254"/>
+ <point x="708" y="254" type="curve" smooth="yes"/>
+ <point x="517" y="254"/>
+ <point x="415" y="413"/>
+ <point x="415" y="698" type="curve" smooth="yes"/>
+ <point x="415" y="759" type="line" smooth="yes"/>
+ <point x="415" y="1047"/>
+ <point x="515" y="1201"/>
+ <point x="706" y="1201" type="curve" smooth="yes"/>
+ <point x="892" y="1201"/>
+ <point x="998" y="1047"/>
+ <point x="998" y="759" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/P_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/P_.glif
new file mode 100644
index 00000000..5b001977
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/P_.glif
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="P" format="2">
+ <unicode hex="0050"/>
+ <advance width="1330"/>
+ <outline>
+ <contour>
+ <point x="694" y="494" type="line"/>
+ <point x="1042" y="494"/>
+ <point x="1253" y="680"/>
+ <point x="1253" y="962" type="curve"/>
+ <point x="1253" y="1247"/>
+ <point x="1042" y="1456"/>
+ <point x="694" y="1456" type="curve"/>
+ <point x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ <point x="452" y="1185" type="line"/>
+ <point x="694" y="1185" type="line"/>
+ <point x="849" y="1185"/>
+ <point x="914" y="1079"/>
+ <point x="914" y="960" type="curve"/>
+ <point x="914" y="847"/>
+ <point x="849" y="765"/>
+ <point x="694" y="765" type="curve"/>
+ <point x="330" y="765" type="line"/>
+ <point x="330" y="494" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Q_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Q_.glif
new file mode 100644
index 00000000..28a96169
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Q_.glif
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="Q" format="2">
+ <unicode hex="0051"/>
+ <advance width="1414"/>
+ <outline>
+ <contour>
+ <point x="922" y="240" type="line"/>
+ <point x="720" y="56" type="line"/>
+ <point x="1116" y="-266" type="line"/>
+ <point x="1325" y="-82" type="line"/>
+ </contour>
+ <contour>
+ <point x="1339" y="757" type="line"/>
+ <point x="1339" y="1202"/>
+ <point x="1077" y="1476"/>
+ <point x="707" y="1476" type="curve" smooth="yes"/>
+ <point x="335" y="1476"/>
+ <point x="76" y="1202"/>
+ <point x="76" y="757" type="curve"/>
+ <point x="76" y="698" type="line"/>
+ <point x="76" y="253"/>
+ <point x="337" y="-20"/>
+ <point x="709" y="-20" type="curve" smooth="yes"/>
+ <point x="1079" y="-20"/>
+ <point x="1339" y="253"/>
+ <point x="1339" y="698" type="curve"/>
+ </contour>
+ <contour>
+ <point x="999" y="698" type="line"/>
+ <point x="999" y="413"/>
+ <point x="895" y="254"/>
+ <point x="709" y="254" type="curve" smooth="yes"/>
+ <point x="518" y="254"/>
+ <point x="416" y="413"/>
+ <point x="416" y="698" type="curve" smooth="yes"/>
+ <point x="416" y="759" type="line" smooth="yes"/>
+ <point x="416" y="1047"/>
+ <point x="516" y="1201"/>
+ <point x="707" y="1201" type="curve" smooth="yes"/>
+ <point x="893" y="1201"/>
+ <point x="999" y="1047"/>
+ <point x="999" y="759" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/R_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/R_.glif
new file mode 100644
index 00000000..b76ad56e
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/R_.glif
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="R" format="2">
+ <unicode hex="0052"/>
+ <advance width="1327"/>
+ <outline>
+ <contour>
+ <point name="hr00" x="117" y="1456" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="452" y="0" type="line"/>
+ <point x="452" y="1185" type="line"/>
+ <point x="680" y="1185" type="line" smooth="yes"/>
+ <point x="820" y="1185"/>
+ <point x="892" y="1109"/>
+ <point x="892" y="984" type="curve" smooth="yes"/>
+ <point x="892" y="860"/>
+ <point x="821" y="785"/>
+ <point x="680" y="785" type="curve" smooth="yes"/>
+ <point x="328" y="785" type="line"/>
+ <point x="330" y="514" type="line"/>
+ <point x="806" y="514" type="line"/>
+ <point x="915" y="579" type="line"/>
+ <point x="1102" y="649"/>
+ <point x="1227" y="766"/>
+ <point x="1227" y="1016" type="curve" smooth="yes"/>
+ <point x="1227" y="1303"/>
+ <point x="1016" y="1456"/>
+ <point x="680" y="1456" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="919" y="0" type="line"/>
+ <point x="1278" y="0" type="line"/>
+ <point x="1278" y="15" type="line"/>
+ <point x="949" y="646" type="line"/>
+ <point x="594" y="644" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/S_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/S_.glif
new file mode 100644
index 00000000..a2ab850e
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/S_.glif
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="S" format="2">
+ <unicode hex="0053"/>
+ <advance width="1275"/>
+ <outline>
+ <contour>
+ <point name="hr00" x="869" y="387" type="curve" smooth="yes"/>
+ <point x="869" y="309"/>
+ <point x="809" y="244"/>
+ <point x="669" y="244" type="curve" smooth="yes"/>
+ <point x="502" y="244"/>
+ <point x="402" y="301"/>
+ <point x="402" y="472" type="curve"/>
+ <point x="66" y="472" type="line"/>
+ <point x="66" y="127"/>
+ <point x="373" y="-20"/>
+ <point x="669" y="-20" type="curve" smooth="yes"/>
+ <point x="993" y="-20"/>
+ <point x="1204" y="127"/>
+ <point x="1204" y="389" type="curve"/>
+ <point x="1204" y="634"/>
+ <point x="1030" y="772"/>
+ <point x="718" y="870" type="curve"/>
+ <point x="545" y="924"/>
+ <point x="444" y="977"/>
+ <point x="444" y="1064" type="curve"/>
+ <point x="444" y="1144"/>
+ <point x="515" y="1211"/>
+ <point x="656" y="1211" type="curve" smooth="yes"/>
+ <point x="800" y="1211"/>
+ <point x="870" y="1134"/>
+ <point x="870" y="1024" type="curve"/>
+ <point x="1204" y="1024" type="line"/>
+ <point x="1204" y="1299"/>
+ <point x="983" y="1476"/>
+ <point x="663" y="1476" type="curve" smooth="yes"/>
+ <point x="343" y="1476"/>
+ <point x="110" y="1317"/>
+ <point x="110" y="1067" type="curve"/>
+ <point x="110" y="805"/>
+ <point x="344" y="685"/>
+ <point x="603" y="600" type="curve"/>
+ <point x="827" y="527"/>
+ <point x="869" y="478"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/T_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/T_.glif
new file mode 100644
index 00000000..5e0a1c98
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/T_.glif
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="T" format="2">
+ <unicode hex="0054"/>
+ <advance width="1284"/>
+ <outline>
+ <contour>
+ <point x="805" y="1456" type="line"/>
+ <point x="470" y="1456" type="line"/>
+ <point x="470" y="0" type="line"/>
+ <point x="805" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1245" y="1456" type="line"/>
+ <point x="38" y="1456" type="line"/>
+ <point x="38" y="1185" type="line"/>
+ <point x="1245" y="1185" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/U_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/U_.glif
new file mode 100644
index 00000000..202f92f3
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/U_.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="U" format="2">
+ <unicode hex="0055"/>
+ <advance width="1357"/>
+ <outline>
+ <contour>
+ <point x="911" y="1456" type="line"/>
+ <point x="911" y="505" type="line" smooth="yes"/>
+ <point x="911" y="325"/>
+ <point x="829" y="250"/>
+ <point x="679" y="250" type="curve" smooth="yes"/>
+ <point x="530" y="250"/>
+ <point x="445" y="325"/>
+ <point x="445" y="505" type="curve" smooth="yes"/>
+ <point x="445" y="1456" type="line"/>
+ <point x="109" y="1456" type="line"/>
+ <point x="109" y="505" type="line"/>
+ <point x="109" y="164"/>
+ <point x="342" y="-20"/>
+ <point x="679" y="-20" type="curve" smooth="yes"/>
+ <point x="1017" y="-20"/>
+ <point x="1246" y="164"/>
+ <point x="1246" y="505" type="curve"/>
+ <point x="1246" y="1456" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/V_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/V_.glif
new file mode 100644
index 00000000..c242ddd6
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/V_.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="V" format="2">
+ <unicode hex="0056"/>
+ <advance width="1349"/>
+ <outline>
+ <contour>
+ <point x="659" y="343" type="line"/>
+ <point x="610" y="0" type="line"/>
+ <point x="854" y="0" type="line"/>
+ <point x="1350" y="1456" type="line"/>
+ <point x="976" y="1456" type="line"/>
+ </contour>
+ <contour>
+ <point x="372" y="1456" type="line"/>
+ <point x="0" y="1456" type="line"/>
+ <point x="493" y="0" type="line"/>
+ <point x="739" y="0" type="line"/>
+ <point x="688" y="343" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/W_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/W_.glif
new file mode 100644
index 00000000..5cf2b3a6
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/W_.glif
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="W" format="2">
+ <unicode hex="0057"/>
+ <advance width="1784"/>
+ <outline>
+ <contour>
+ <point x="459" y="132" type="line"/>
+ <point x="498" y="0" type="line"/>
+ <point x="684" y="0" type="line"/>
+ <point x="993" y="1343" type="line"/>
+ <point x="918" y="1456" type="line"/>
+ <point x="748" y="1456" type="line"/>
+ </contour>
+ <contour>
+ <point x="359" y="1456" type="line"/>
+ <point x="26" y="1456" type="line"/>
+ <point x="340" y="0" type="line"/>
+ <point x="552" y="0" type="line"/>
+ <point x="601" y="122" type="line"/>
+ </contour>
+ <contour>
+ <point x="1183" y="129" type="line"/>
+ <point x="1230" y="0" type="line"/>
+ <point x="1442" y="0" type="line"/>
+ <point x="1755" y="1456" type="line"/>
+ <point x="1423" y="1456" type="line"/>
+ </contour>
+ <contour>
+ <point x="1032" y="1456" type="line"/>
+ <point x="863" y="1456" type="line"/>
+ <point x="785" y="1345" type="line"/>
+ <point x="1098" y="0" type="line"/>
+ <point x="1284" y="0" type="line"/>
+ <point x="1326" y="124" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/X_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/X_.glif
new file mode 100644
index 00000000..9682d90e
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/X_.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="X" format="2">
+ <unicode hex="0058"/>
+ <advance width="1306"/>
+ <outline>
+ <contour>
+ <point x="404" y="1456" type="line"/>
+ <point x="21" y="1456" type="line"/>
+ <point x="433" y="734" type="line"/>
+ <point x="10" y="0" type="line"/>
+ <point x="397" y="0" type="line"/>
+ <point x="653" y="493" type="line"/>
+ <point x="909" y="0" type="line"/>
+ <point x="1296" y="0" type="line"/>
+ <point x="873" y="734" type="line"/>
+ <point x="1285" y="1456" type="line"/>
+ <point x="902" y="1456" type="line"/>
+ <point x="653" y="972" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Y_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Y_.glif
new file mode 100644
index 00000000..0b738ae8
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Y_.glif
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="Y" format="2">
+ <unicode hex="0059"/>
+ <advance width="1280"/>
+ <outline>
+ <contour>
+ <point x="361" y="1456" type="line"/>
+ <point x="-2" y="1456" type="line"/>
+ <point x="470" y="523" type="line"/>
+ <point x="470" y="0" type="line"/>
+ <point x="810" y="0" type="line"/>
+ <point x="810" y="523" type="line"/>
+ <point x="1282" y="1456" type="line"/>
+ <point x="919" y="1456" type="line"/>
+ <point x="640" y="824" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Z_.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Z_.glif
new file mode 100644
index 00000000..82d66471
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/Z_.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="Z" format="2">
+ <unicode hex="005A"/>
+ <advance width="1247"/>
+ <outline>
+ <contour>
+ <point x="1195" y="270" type="line"/>
+ <point x="149" y="270" type="line"/>
+ <point x="149" y="0" type="line"/>
+ <point x="1195" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1185" y="1276" type="line"/>
+ <point x="1185" y="1456" type="line"/>
+ <point x="954" y="1456" type="line"/>
+ <point x="69" y="185" type="line"/>
+ <point x="69" y="0" type="line"/>
+ <point x="309" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1075" y="1456" type="line"/>
+ <point x="66" y="1456" type="line"/>
+ <point x="66" y="1185" type="line"/>
+ <point x="1075" y="1185" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/a.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/a.glif
new file mode 100644
index 00000000..885bf08e
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/a.glif
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="a" format="2">
+ <unicode hex="0061"/>
+ <advance width="1091"/>
+ <outline>
+ <contour>
+ <point name="hr00" x="670" y="272" type="line"/>
+ <point x="670" y="169"/>
+ <point x="684" y="66"/>
+ <point x="715" y="0" type="curve"/>
+ <point x="1038" y="0" type="line"/>
+ <point x="1038" y="17" type="line"/>
+ <point x="1009" y="71"/>
+ <point x="993" y="132"/>
+ <point x="993" y="273" type="curve" smooth="yes"/>
+ <point x="993" y="716" type="line"/>
+ <point x="993" y="973"/>
+ <point x="803" y="1102"/>
+ <point x="548" y="1102" type="curve" smooth="yes"/>
+ <point x="262" y="1102"/>
+ <point x="78" y="950"/>
+ <point x="78" y="749" type="curve"/>
+ <point x="400" y="749" type="line"/>
+ <point x="400" y="828"/>
+ <point x="448" y="867"/>
+ <point x="531" y="867" type="curve" smooth="yes"/>
+ <point x="629" y="867"/>
+ <point x="670" y="809"/>
+ <point x="670" y="718" type="curve"/>
+ </contour>
+ <contour>
+ <point x="710" y="661" type="line"/>
+ <point x="558" y="661" type="line"/>
+ <point x="216" y="661"/>
+ <point x="53" y="528"/>
+ <point x="53" y="305" type="curve" smooth="yes"/>
+ <point x="53" y="113"/>
+ <point x="218" y="-20"/>
+ <point x="420" y="-20" type="curve" smooth="yes"/>
+ <point x="627" y="-20"/>
+ <point x="709" y="108"/>
+ <point x="758" y="214" type="curve"/>
+ <point x="683" y="352" type="line"/>
+ <point x="683" y="297"/>
+ <point x="612" y="220"/>
+ <point x="495" y="220" type="curve" smooth="yes"/>
+ <point x="424" y="220"/>
+ <point x="375" y="262"/>
+ <point x="375" y="323" type="curve" smooth="yes"/>
+ <point x="375" y="405"/>
+ <point x="425" y="481"/>
+ <point x="560" y="481" type="curve"/>
+ <point x="712" y="481" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/b.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/b.glif
new file mode 100644
index 00000000..728335cb
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/b.glif
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="b" format="2">
+ <unicode hex="0062"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="102" y="1536" type="line"/>
+ <point x="102" y="0" type="line"/>
+ <point x="391" y="0" type="line"/>
+ <point x="424" y="266" type="line"/>
+ <point x="424" y="1536" type="line"/>
+ </contour>
+ <contour>
+ <point x="1095" y="553" type="line"/>
+ <point x="1095" y="870"/>
+ <point x="961" y="1102"/>
+ <point x="673" y="1102" type="curve" smooth="yes"/>
+ <point x="412" y="1102"/>
+ <point x="306" y="856"/>
+ <point x="264" y="554" type="curve"/>
+ <point x="264" y="529" type="line"/>
+ <point x="306" y="225"/>
+ <point x="412" y="-20"/>
+ <point x="675" y="-20" type="curve" smooth="yes"/>
+ <point x="961" y="-20"/>
+ <point x="1095" y="205"/>
+ <point x="1095" y="532" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="773" y="532" type="line"/>
+ <point x="773" y="358"/>
+ <point x="745" y="239"/>
+ <point x="594" y="239" type="curve" smooth="yes"/>
+ <point x="445" y="239"/>
+ <point x="394" y="335"/>
+ <point x="394" y="502" type="curve" smooth="yes"/>
+ <point x="394" y="581" type="line" smooth="yes"/>
+ <point x="394" y="744"/>
+ <point x="445" y="842"/>
+ <point x="592" y="842" type="curve" smooth="yes"/>
+ <point x="741" y="842"/>
+ <point x="773" y="710"/>
+ <point x="773" y="553" type="curve" smooth="yes"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/c.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/c.glif
new file mode 100644
index 00000000..33db7288
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/c.glif
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="c" format="2">
+ <unicode hex="0063"/>
+ <advance width="1066"/>
+ <outline>
+ <contour>
+ <point x="555" y="240" type="curve" smooth="yes"/>
+ <point x="404" y="240"/>
+ <point x="379" y="369"/>
+ <point x="379" y="529" type="curve" smooth="yes"/>
+ <point x="379" y="552" type="line" smooth="yes"/>
+ <point x="379" y="708"/>
+ <point x="405" y="842"/>
+ <point x="553" y="842" type="curve"/>
+ <point x="661" y="842"/>
+ <point x="714" y="765"/>
+ <point x="714" y="668" type="curve"/>
+ <point x="1016" y="668" type="line"/>
+ <point x="1016" y="943"/>
+ <point x="829" y="1102"/>
+ <point x="560" y="1102" type="curve" smooth="yes"/>
+ <point x="225" y="1102"/>
+ <point x="57" y="865"/>
+ <point x="57" y="552" type="curve" smooth="yes"/>
+ <point x="57" y="529" type="line" smooth="yes"/>
+ <point x="57" y="216"/>
+ <point x="225" y="-20"/>
+ <point x="562" y="-20" type="curve"/>
+ <point x="819" y="-20"/>
+ <point x="1016" y="142"/>
+ <point x="1016" y="386" type="curve"/>
+ <point x="714" y="386" type="line"/>
+ <point x="714" y="294"/>
+ <point x="653" y="240"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/contents.plist b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/contents.plist
new file mode 100644
index 00000000..431aca78
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/contents.plist
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>A</key>
+ <string>A_.glif</string>
+ <key>B</key>
+ <string>B_.glif</string>
+ <key>C</key>
+ <string>C_.glif</string>
+ <key>D</key>
+ <string>D_.glif</string>
+ <key>E</key>
+ <string>E_.glif</string>
+ <key>F</key>
+ <string>F_.glif</string>
+ <key>G</key>
+ <string>G_.glif</string>
+ <key>H</key>
+ <string>H_.glif</string>
+ <key>I</key>
+ <string>I_.glif</string>
+ <key>J</key>
+ <string>J_.glif</string>
+ <key>K</key>
+ <string>K_.glif</string>
+ <key>L</key>
+ <string>L_.glif</string>
+ <key>M</key>
+ <string>M_.glif</string>
+ <key>N</key>
+ <string>N_.glif</string>
+ <key>O</key>
+ <string>O_.glif</string>
+ <key>P</key>
+ <string>P_.glif</string>
+ <key>Q</key>
+ <string>Q_.glif</string>
+ <key>R</key>
+ <string>R_.glif</string>
+ <key>S</key>
+ <string>S_.glif</string>
+ <key>T</key>
+ <string>T_.glif</string>
+ <key>U</key>
+ <string>U_.glif</string>
+ <key>V</key>
+ <string>V_.glif</string>
+ <key>W</key>
+ <string>W_.glif</string>
+ <key>X</key>
+ <string>X_.glif</string>
+ <key>Y</key>
+ <string>Y_.glif</string>
+ <key>Z</key>
+ <string>Z_.glif</string>
+ <key>a</key>
+ <string>a.glif</string>
+ <key>b</key>
+ <string>b.glif</string>
+ <key>c</key>
+ <string>c.glif</string>
+ <key>d</key>
+ <string>d.glif</string>
+ <key>e</key>
+ <string>e.glif</string>
+ <key>f</key>
+ <string>f.glif</string>
+ <key>g</key>
+ <string>g.glif</string>
+ <key>h</key>
+ <string>h.glif</string>
+ <key>i</key>
+ <string>i.glif</string>
+ <key>j</key>
+ <string>j.glif</string>
+ <key>k</key>
+ <string>k.glif</string>
+ <key>l</key>
+ <string>l.glif</string>
+ <key>m</key>
+ <string>m.glif</string>
+ <key>n</key>
+ <string>n.glif</string>
+ <key>o</key>
+ <string>o.glif</string>
+ <key>p</key>
+ <string>p.glif</string>
+ <key>q</key>
+ <string>q.glif</string>
+ <key>r</key>
+ <string>r.glif</string>
+ <key>s</key>
+ <string>s.glif</string>
+ <key>space</key>
+ <string>space.glif</string>
+ <key>t</key>
+ <string>t.glif</string>
+ <key>u</key>
+ <string>u.glif</string>
+ <key>v</key>
+ <string>v.glif</string>
+ <key>w</key>
+ <string>w.glif</string>
+ <key>x</key>
+ <string>x.glif</string>
+ <key>y</key>
+ <string>y.glif</string>
+ <key>z</key>
+ <string>z.glif</string>
+ </dict>
+</plist>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/d.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/d.glif
new file mode 100644
index 00000000..9c7ac795
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/d.glif
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="d" format="2">
+ <unicode hex="0064"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="728" y="248" type="line"/>
+ <point x="761" y="0" type="line"/>
+ <point x="1051" y="0" type="line"/>
+ <point x="1051" y="1536" type="line"/>
+ <point x="728" y="1536" type="line"/>
+ </contour>
+ <contour>
+ <point x="57" y="528" type="line"/>
+ <point x="57" y="213"/>
+ <point x="205" y="-20"/>
+ <point x="477" y="-20" type="curve" smooth="yes"/>
+ <point x="728" y="-20"/>
+ <point x="848" y="227"/>
+ <point x="889" y="520" type="curve"/>
+ <point x="889" y="545" type="line"/>
+ <point x="848" y="858"/>
+ <point x="728" y="1102"/>
+ <point x="479" y="1102" type="curve" smooth="yes"/>
+ <point x="205" y="1102"/>
+ <point x="57" y="878"/>
+ <point x="57" y="549" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="379" y="549" type="line"/>
+ <point x="379" y="717"/>
+ <point x="427" y="842"/>
+ <point x="561" y="842" type="curve" smooth="yes"/>
+ <point x="695" y="842"/>
+ <point x="759" y="748"/>
+ <point x="759" y="572" type="curve" smooth="yes"/>
+ <point x="759" y="493" type="line" smooth="yes"/>
+ <point x="759" y="339"/>
+ <point x="694" y="240"/>
+ <point x="559" y="240" type="curve" smooth="yes"/>
+ <point x="423" y="240"/>
+ <point x="379" y="366"/>
+ <point x="379" y="528" type="curve" smooth="yes"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/e.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/e.glif
new file mode 100644
index 00000000..71ee028b
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/e.glif
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="e" format="2">
+ <unicode hex="0065"/>
+ <advance width="1113"/>
+ <outline>
+ <contour>
+ <point x="616" y="-20" type="curve" smooth="yes"/>
+ <point x="825" y="-20"/>
+ <point x="973" y="75"/>
+ <point x="1041" y="170" type="curve"/>
+ <point x="891" y="352" type="line"/>
+ <point x="827" y="272"/>
+ <point x="733" y="240"/>
+ <point x="637" y="240" type="curve" smooth="yes"/>
+ <point x="481" y="240"/>
+ <point x="387" y="345"/>
+ <point x="387" y="505" type="curve" smooth="yes"/>
+ <point x="387" y="543" type="line" smooth="yes"/>
+ <point x="387" y="704"/>
+ <point x="430" y="842"/>
+ <point x="579" y="842" type="curve" smooth="yes"/>
+ <point x="694" y="842"/>
+ <point x="753" y="779"/>
+ <point x="753" y="672" type="curve" smooth="yes"/>
+ <point x="753" y="646" type="line"/>
+ <point name="hr01" x="192" y="646" type="line"/>
+ <point x="192" y="435" type="line"/>
+ <point x="1068" y="435" type="line"/>
+ <point x="1068" y="572" type="line" smooth="yes"/>
+ <point x="1068" y="897"/>
+ <point x="890" y="1102"/>
+ <point x="581" y="1102" type="curve" smooth="yes"/>
+ <point x="246" y="1102"/>
+ <point x="65" y="859"/>
+ <point name="hr02" x="65" y="543" type="curve" smooth="yes"/>
+ <point x="65" y="505" type="line" smooth="yes"/>
+ <point x="65" y="221"/>
+ <point x="268" y="-20"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/f.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/f.glif
new file mode 100644
index 00000000..deeb2d6e
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/f.glif
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="f" format="2">
+ <unicode hex="0066"/>
+ <advance width="740"/>
+ <outline>
+ <contour>
+ <point name="hr00" x="499" y="0" type="line"/>
+ <point x="499" y="1170" type="line"/>
+ <point x="499" y="1252"/>
+ <point x="555" y="1297"/>
+ <point x="655" y="1297" type="curve" smooth="yes"/>
+ <point x="691" y="1297"/>
+ <point x="716" y="1294"/>
+ <point x="740" y="1288" type="curve"/>
+ <point x="740" y="1536" type="line"/>
+ <point x="692" y="1548"/>
+ <point x="641" y="1557"/>
+ <point x="585" y="1557" type="curve" smooth="yes"/>
+ <point x="334" y="1557"/>
+ <point x="176" y="1421"/>
+ <point x="176" y="1170" type="curve"/>
+ <point x="176" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="711" y="1082" type="line"/>
+ <point x="18" y="1082" type="line"/>
+ <point x="18" y="848" type="line"/>
+ <point x="711" y="848" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/g.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/g.glif
new file mode 100644
index 00000000..76187215
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/g.glif
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="g" format="2">
+ <unicode hex="0067"/>
+ <advance width="1176"/>
+ <outline>
+ <contour>
+ <point x="782" y="1082" type="line"/>
+ <point x="751" y="826" type="line"/>
+ <point x="751" y="44" type="line" smooth="yes"/>
+ <point x="751" y="-96"/>
+ <point x="669" y="-177"/>
+ <point x="521" y="-177" type="curve" smooth="yes"/>
+ <point x="411" y="-177"/>
+ <point x="326" y="-128"/>
+ <point x="268" y="-66" type="curve"/>
+ <point x="131" y="-264" type="line"/>
+ <point x="221" y="-370"/>
+ <point x="392" y="-426"/>
+ <point x="533" y="-426" type="curve" smooth="yes"/>
+ <point x="856" y="-426"/>
+ <point x="1074" y="-258"/>
+ <point x="1074" y="42" type="curve" smooth="yes"/>
+ <point x="1074" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="60" y="528" type="line"/>
+ <point x="60" y="213"/>
+ <point x="228" y="-20"/>
+ <point x="500" y="-20" type="curve" smooth="yes"/>
+ <point x="763" y="-20"/>
+ <point x="869" y="227"/>
+ <point x="912" y="520" type="curve"/>
+ <point x="912" y="545" type="line"/>
+ <point x="869" y="858"/>
+ <point x="795" y="1102"/>
+ <point x="502" y="1102" type="curve" smooth="yes"/>
+ <point x="228" y="1102"/>
+ <point x="60" y="878"/>
+ <point x="60" y="549" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="382" y="549" type="line"/>
+ <point x="382" y="717"/>
+ <point x="450" y="842"/>
+ <point x="584" y="842" type="curve" smooth="yes"/>
+ <point x="731" y="842"/>
+ <point x="792" y="748"/>
+ <point x="792" y="572" type="curve" smooth="yes"/>
+ <point x="792" y="493" type="line" smooth="yes"/>
+ <point x="792" y="339"/>
+ <point x="731" y="240"/>
+ <point x="582" y="240" type="curve" smooth="yes"/>
+ <point x="446" y="240"/>
+ <point x="382" y="366"/>
+ <point x="382" y="528" type="curve" smooth="yes"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/h.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/h.glif
new file mode 100644
index 00000000..58e97548
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/h.glif
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="h" format="2">
+ <unicode hex="0068"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="415" y="1536" type="line"/>
+ <point x="93" y="1536" type="line"/>
+ <point x="93" y="0" type="line"/>
+ <point x="415" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="374" y="578" type="line"/>
+ <point x="374" y="729"/>
+ <point x="413" y="842"/>
+ <point x="573" y="842" type="curve" smooth="yes"/>
+ <point x="674" y="842"/>
+ <point x="733" y="806"/>
+ <point x="733" y="674" type="curve" smooth="yes"/>
+ <point x="733" y="0" type="line"/>
+ <point x="1056" y="0" type="line"/>
+ <point x="1056" y="672" type="line" smooth="yes"/>
+ <point x="1056" y="986"/>
+ <point x="909" y="1102"/>
+ <point x="695" y="1102" type="curve" smooth="yes"/>
+ <point x="454" y="1102"/>
+ <point x="295" y="880"/>
+ <point x="295" y="576" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/i.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/i.glif
new file mode 100644
index 00000000..be7bc25f
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/i.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="i" format="2">
+ <unicode hex="0069"/>
+ <advance width="557"/>
+ <outline>
+ <contour>
+ <point x="440" y="1082" type="line"/>
+ <point x="117" y="1082" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="440" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="98" y="1361" type="curve" smooth="yes"/>
+ <point x="98" y="1265"/>
+ <point x="170" y="1197"/>
+ <point x="277" y="1197" type="curve" smooth="yes"/>
+ <point x="384" y="1197"/>
+ <point x="456" y="1265"/>
+ <point x="456" y="1361" type="curve" smooth="yes"/>
+ <point x="456" y="1457"/>
+ <point x="384" y="1525"/>
+ <point x="277" y="1525" type="curve" smooth="yes"/>
+ <point x="170" y="1525"/>
+ <point x="98" y="1457"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/j.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/j.glif
new file mode 100644
index 00000000..1ac2ef3b
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/j.glif
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="j" format="2">
+ <unicode hex="006A"/>
+ <advance width="547"/>
+ <outline>
+ <contour>
+ <point x="122" y="1082" type="line"/>
+ <point x="122" y="-35" type="line"/>
+ <point x="122" y="-129"/>
+ <point x="73" y="-174"/>
+ <point x="-16" y="-174" type="curve" smooth="yes"/>
+ <point x="-49" y="-174"/>
+ <point x="-77" y="-170"/>
+ <point x="-110" y="-165" type="curve"/>
+ <point x="-110" y="-420" type="line"/>
+ <point x="-55" y="-433"/>
+ <point x="-10" y="-437"/>
+ <point x="45" y="-437" type="curve" smooth="yes"/>
+ <point x="294" y="-437"/>
+ <point x="445" y="-295"/>
+ <point x="445" y="-35" type="curve"/>
+ <point x="445" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="98" y="1361" type="curve" smooth="yes"/>
+ <point x="98" y="1265"/>
+ <point x="170" y="1197"/>
+ <point x="277" y="1197" type="curve" smooth="yes"/>
+ <point x="384" y="1197"/>
+ <point x="456" y="1265"/>
+ <point x="456" y="1361" type="curve" smooth="yes"/>
+ <point x="456" y="1457"/>
+ <point x="384" y="1525"/>
+ <point x="277" y="1525" type="curve" smooth="yes"/>
+ <point x="170" y="1525"/>
+ <point x="98" y="1457"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/k.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/k.glif
new file mode 100644
index 00000000..9f329d43
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/k.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="k" format="2">
+ <unicode hex="006B"/>
+ <advance width="1112"/>
+ <outline>
+ <contour>
+ <point x="424" y="1537" type="line"/>
+ <point x="102" y="1537" type="line"/>
+ <point x="102" y="0" type="line"/>
+ <point x="424" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1112" y="1082" type="line"/>
+ <point x="726" y="1082" type="line"/>
+ <point x="465" y="766" type="line"/>
+ <point x="261" y="499" type="line"/>
+ <point x="394" y="285" type="line"/>
+ <point x="643" y="531" type="line"/>
+ </contour>
+ <contour>
+ <point x="771" y="0" type="line"/>
+ <point x="1140" y="0" type="line"/>
+ <point x="706" y="670" type="line"/>
+ <point x="473" y="493" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/l.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/l.glif
new file mode 100644
index 00000000..3156475c
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/l.glif
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="l" format="2">
+ <unicode hex="006C"/>
+ <advance width="557"/>
+ <outline>
+ <contour>
+ <point x="440" y="1536" type="line"/>
+ <point x="117" y="1536" type="line"/>
+ <point x="117" y="0" type="line"/>
+ <point x="440" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/m.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/m.glif
new file mode 100644
index 00000000..93ed63a9
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/m.glif
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="m" format="2">
+ <unicode hex="006D"/>
+ <advance width="1767"/>
+ <outline>
+ <contour>
+ <point x="424" y="853" type="line"/>
+ <point x="404" y="1082" type="line"/>
+ <point x="102" y="1082" type="line"/>
+ <point x="102" y="0" type="line"/>
+ <point x="424" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="383" y="578" type="line"/>
+ <point x="383" y="729"/>
+ <point x="452" y="842"/>
+ <point x="581" y="842" type="curve" smooth="yes"/>
+ <point x="670" y="842"/>
+ <point x="722" y="816"/>
+ <point x="722" y="679" type="curve"/>
+ <point x="722" y="0" type="line"/>
+ <point x="1044" y="0" type="line"/>
+ <point x="1044" y="722" type="line"/>
+ <point x="1044" y="992"/>
+ <point x="914" y="1102"/>
+ <point x="724" y="1102" type="curve" smooth="yes"/>
+ <point x="450" y="1102"/>
+ <point x="304" y="880"/>
+ <point x="304" y="576" type="curve"/>
+ </contour>
+ <contour>
+ <point x="1010" y="578" type="line"/>
+ <point x="1010" y="729"/>
+ <point x="1072" y="842"/>
+ <point x="1202" y="842" type="curve" smooth="yes"/>
+ <point x="1289" y="842"/>
+ <point x="1342" y="815"/>
+ <point x="1342" y="681" type="curve" smooth="yes"/>
+ <point x="1342" y="0" type="line"/>
+ <point x="1665" y="0" type="line"/>
+ <point x="1665" y="681" type="line" smooth="yes"/>
+ <point x="1665" y="995"/>
+ <point x="1526" y="1102"/>
+ <point x="1324" y="1102" type="curve" smooth="yes"/>
+ <point x="1050" y="1102"/>
+ <point x="911" y="880"/>
+ <point x="911" y="576" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/n.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/n.glif
new file mode 100644
index 00000000..092570ba
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/n.glif
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="n" format="2">
+ <unicode hex="006E"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="416" y="851" type="line"/>
+ <point x="396" y="1082" type="line"/>
+ <point x="94" y="1082" type="line"/>
+ <point x="94" y="0" type="line"/>
+ <point x="416" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="375" y="578" type="line"/>
+ <point x="375" y="729"/>
+ <point x="431" y="842"/>
+ <point x="574" y="842" type="curve" smooth="yes"/>
+ <point x="675" y="842"/>
+ <point x="733" y="812"/>
+ <point x="733" y="682" type="curve"/>
+ <point x="733" y="0" type="line"/>
+ <point x="1056" y="0" type="line"/>
+ <point x="1056" y="681" type="line"/>
+ <point x="1056" y="995"/>
+ <point x="918" y="1102"/>
+ <point x="716" y="1102" type="curve" smooth="yes"/>
+ <point x="464" y="1102"/>
+ <point x="296" y="907"/>
+ <point x="296" y="576" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/o.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/o.glif
new file mode 100644
index 00000000..b5836f73
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/o.glif
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="o" format="2">
+ <unicode hex="006F"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="57" y="530" type="line"/>
+ <point x="57" y="214"/>
+ <point x="241" y="-20"/>
+ <point x="577" y="-20" type="curve" smooth="yes"/>
+ <point x="912" y="-20"/>
+ <point x="1095" y="214"/>
+ <point x="1095" y="530" type="curve"/>
+ <point x="1095" y="551" type="line"/>
+ <point x="1095" y="867"/>
+ <point x="912" y="1102"/>
+ <point x="575" y="1102" type="curve" smooth="yes"/>
+ <point x="241" y="1102"/>
+ <point x="57" y="867"/>
+ <point x="57" y="551" type="curve"/>
+ </contour>
+ <contour>
+ <point x="379" y="551" type="line"/>
+ <point x="379" y="709"/>
+ <point x="427" y="842"/>
+ <point x="575" y="842" type="curve" smooth="yes"/>
+ <point x="726" y="842"/>
+ <point x="773" y="709"/>
+ <point x="773" y="551" type="curve"/>
+ <point x="773" y="530" type="line"/>
+ <point x="773" y="367"/>
+ <point x="725" y="240"/>
+ <point x="577" y="240" type="curve" smooth="yes"/>
+ <point x="426" y="240"/>
+ <point x="379" y="367"/>
+ <point x="379" y="530" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/p.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/p.glif
new file mode 100644
index 00000000..49894346
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/p.glif
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="p" format="2">
+ <unicode hex="0070"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="424" y="874" type="line"/>
+ <point x="402" y="1082" type="line"/>
+ <point x="102" y="1082" type="line"/>
+ <point x="102" y="-416" type="line"/>
+ <point x="424" y="-416" type="line"/>
+ </contour>
+ <contour>
+ <point x="1095" y="554" type="line"/>
+ <point x="1095" y="883"/>
+ <point x="948" y="1102"/>
+ <point x="673" y="1102" type="curve" smooth="yes"/>
+ <point x="412" y="1102"/>
+ <point x="306" y="863"/>
+ <point x="264" y="550" type="curve"/>
+ <point x="264" y="523" type="line"/>
+ <point x="306" y="231"/>
+ <point x="412" y="-20"/>
+ <point x="675" y="-20" type="curve" smooth="yes"/>
+ <point x="948" y="-20"/>
+ <point x="1095" y="218"/>
+ <point x="1095" y="533" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="773" y="533" type="line"/>
+ <point x="773" y="371"/>
+ <point x="729" y="240"/>
+ <point x="594" y="240" type="curve" smooth="yes"/>
+ <point x="445" y="240"/>
+ <point x="394" y="343"/>
+ <point x="394" y="495" type="curve" smooth="yes"/>
+ <point x="394" y="577" type="line" smooth="yes"/>
+ <point x="394" y="753"/>
+ <point x="445" y="842"/>
+ <point x="592" y="842" type="curve" smooth="yes"/>
+ <point x="725" y="842"/>
+ <point x="773" y="722"/>
+ <point x="773" y="554" type="curve" smooth="yes"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/q.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/q.glif
new file mode 100644
index 00000000..78a39141
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/q.glif
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="q" format="2">
+ <unicode hex="0071"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="728" y="-416" type="line"/>
+ <point x="1051" y="-416" type="line"/>
+ <point x="1051" y="1082" type="line"/>
+ <point x="771" y="1082" type="line"/>
+ <point x="728" y="855" type="line"/>
+ </contour>
+ <contour>
+ <point x="57" y="531" type="line"/>
+ <point x="57" y="216"/>
+ <point x="205" y="-20"/>
+ <point x="477" y="-20" type="curve" smooth="yes"/>
+ <point x="740" y="-20"/>
+ <point x="846" y="230"/>
+ <point x="889" y="523" type="curve"/>
+ <point x="889" y="548" type="line"/>
+ <point x="846" y="861"/>
+ <point x="740" y="1102"/>
+ <point x="479" y="1102" type="curve" smooth="yes"/>
+ <point x="205" y="1102"/>
+ <point x="57" y="881"/>
+ <point x="57" y="552" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="379" y="552" type="line"/>
+ <point x="379" y="720"/>
+ <point x="427" y="842"/>
+ <point x="561" y="842" type="curve" smooth="yes"/>
+ <point x="708" y="842"/>
+ <point x="759" y="751"/>
+ <point x="759" y="575" type="curve" smooth="yes"/>
+ <point x="759" y="496" type="line" smooth="yes"/>
+ <point x="759" y="342"/>
+ <point x="708" y="240"/>
+ <point x="559" y="240" type="curve" smooth="yes"/>
+ <point x="423" y="240"/>
+ <point x="379" y="369"/>
+ <point x="379" y="531" type="curve" smooth="yes"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/r.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/r.glif
new file mode 100644
index 00000000..e4e388aa
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/r.glif
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="r" format="2">
+ <unicode hex="0072"/>
+ <advance width="767"/>
+ <outline>
+ <contour>
+ <point x="424" y="814" type="line"/>
+ <point x="404" y="1082" type="line"/>
+ <point x="102" y="1082" type="line"/>
+ <point x="102" y="0" type="line"/>
+ <point x="424" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="745" y="1090" type="line"/>
+ <point x="721" y="1098"/>
+ <point x="685" y="1102"/>
+ <point x="653" y="1102" type="curve"/>
+ <point x="459" y="1102"/>
+ <point x="343" y="902"/>
+ <point x="343" y="612" type="curve"/>
+ <point x="403" y="572" type="line"/>
+ <point x="403" y="714"/>
+ <point x="472" y="785"/>
+ <point x="631" y="785" type="curve"/>
+ <point x="661" y="785"/>
+ <point x="712" y="780"/>
+ <point x="740" y="777" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/s.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/s.glif
new file mode 100644
index 00000000..c46d75ce
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/s.glif
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="s" format="2">
+ <unicode hex="0073"/>
+ <advance width="1051"/>
+ <outline>
+ <contour>
+ <point x="673" y="304" type="curve" smooth="yes"/>
+ <point x="673" y="244"/>
+ <point x="622" y="203"/>
+ <point x="525" y="203" type="curve" smooth="yes"/>
+ <point x="424" y="203"/>
+ <point x="348" y="247"/>
+ <point x="344" y="347" type="curve"/>
+ <point x="42" y="347" type="line"/>
+ <point x="42" y="173"/>
+ <point x="208" y="-20"/>
+ <point x="517" y="-20" type="curve" smooth="yes"/>
+ <point x="803" y="-20"/>
+ <point x="984" y="123"/>
+ <point x="984" y="314" type="curve"/>
+ <point x="984" y="543"/>
+ <point x="792" y="619"/>
+ <point x="569" y="659" type="curve" smooth="yes"/>
+ <point x="435" y="683"/>
+ <point x="382" y="719"/>
+ <point x="382" y="777" type="curve"/>
+ <point x="382" y="839"/>
+ <point x="438" y="880"/>
+ <point x="516" y="880" type="curve" smooth="yes"/>
+ <point x="620" y="880"/>
+ <point x="662" y="832"/>
+ <point x="662" y="750" type="curve"/>
+ <point x="984" y="750" type="line"/>
+ <point x="984" y="958"/>
+ <point x="805" y="1102"/>
+ <point x="517" y="1102" type="curve" smooth="yes"/>
+ <point x="238" y="1102"/>
+ <point x="76" y="943"/>
+ <point x="76" y="760" type="curve"/>
+ <point x="76" y="570"/>
+ <point x="243" y="472"/>
+ <point x="458" y="426" type="curve" smooth="yes"/>
+ <point x="629" y="389"/>
+ <point x="673" y="359"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/space.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/space.glif
new file mode 100644
index 00000000..52850f20
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/space.glif
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="space" format="1">
+ <advance width="510"/>
+ <unicode hex="0020"/>
+ <outline>
+ </outline>
+ <lib>
+ <dict>
+ <key>com.typemytype.robofont.guides</key>
+ <array>
+ <dict>
+ <key>angle</key>
+ <real>0</real>
+ <key>isGlobal</key>
+ <false/>
+ <key>magnetic</key>
+ <integer>5</integer>
+ <key>x</key>
+ <real>0</real>
+ <key>y</key>
+ <real>901</real>
+ </dict>
+ <dict>
+ <key>angle</key>
+ <real>0</real>
+ <key>isGlobal</key>
+ <false/>
+ <key>magnetic</key>
+ <integer>5</integer>
+ <key>x</key>
+ <real>0</real>
+ <key>y</key>
+ <real>555</real>
+ </dict>
+ </array>
+ </dict>
+ </lib>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/t.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/t.glif
new file mode 100644
index 00000000..2639d1d4
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/t.glif
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="t" format="2">
+ <unicode hex="0074"/>
+ <advance width="700"/>
+ <outline>
+ <contour>
+ <point x="658" y="1082" type="line"/>
+ <point x="12" y="1082" type="line"/>
+ <point x="12" y="848" type="line"/>
+ <point x="658" y="848" type="line"/>
+ </contour>
+ <contour>
+ <point x="156" y="1351" type="line"/>
+ <point x="156" y="311" type="line"/>
+ <point x="156" y="76"/>
+ <point x="279" y="-20"/>
+ <point x="487" y="-20" type="curve" smooth="yes"/>
+ <point x="558" y="-20"/>
+ <point x="617" y="-10"/>
+ <point x="672" y="9" type="curve"/>
+ <point x="672" y="250" type="line"/>
+ <point x="650" y="246"/>
+ <point x="625" y="244"/>
+ <point x="588" y="244" type="curve" smooth="yes"/>
+ <point x="508" y="244"/>
+ <point x="478" y="267"/>
+ <point x="478" y="353" type="curve"/>
+ <point x="478" y="1351" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/u.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/u.glif
new file mode 100644
index 00000000..4ee30722
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/u.glif
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="u" format="2">
+ <unicode hex="0075"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="734" y="263" type="line"/>
+ <point x="755" y="0" type="line"/>
+ <point x="1057" y="0" type="line"/>
+ <point x="1057" y="1082" type="line"/>
+ <point x="734" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="767" y="483" type="line"/>
+ <point x="767" y="344"/>
+ <point x="718" y="240"/>
+ <point x="557" y="240" type="curve" smooth="yes"/>
+ <point x="469" y="240"/>
+ <point x="416" y="285"/>
+ <point x="416" y="380" type="curve"/>
+ <point x="416" y="1082" type="line"/>
+ <point x="94" y="1082" type="line"/>
+ <point x="94" y="382" type="line"/>
+ <point x="94" y="96"/>
+ <point x="241" y="-20"/>
+ <point x="455" y="-20" type="curve" smooth="yes"/>
+ <point x="716" y="-20"/>
+ <point x="854" y="194"/>
+ <point x="854" y="485" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/v.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/v.glif
new file mode 100644
index 00000000..2c06870e
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/v.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="v" format="2">
+ <unicode hex="0076"/>
+ <advance width="1051"/>
+ <outline>
+ <contour>
+ <point x="483" y="231" type="line"/>
+ <point x="483" y="0" type="line"/>
+ <point x="685" y="0" type="line"/>
+ <point x="1042" y="1082" type="line"/>
+ <point x="704" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="345" y="1082" type="line"/>
+ <point x="6" y="1082" type="line"/>
+ <point x="363" y="0" type="line"/>
+ <point x="565" y="0" type="line"/>
+ <point x="565" y="231" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/w.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/w.glif
new file mode 100644
index 00000000..08b32ac2
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/w.glif
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="w" format="2">
+ <unicode hex="0077"/>
+ <advance width="1493"/>
+ <outline>
+ <contour>
+ <point x="422" y="322" type="line"/>
+ <point x="392" y="0" type="line"/>
+ <point x="557" y="0" type="line"/>
+ <point x="764" y="701" type="line"/>
+ <point x="833" y="1082" type="line"/>
+ <point x="631" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="333" y="1082" type="line"/>
+ <point x="24" y="1082" type="line"/>
+ <point x="286" y="0" type="line"/>
+ <point x="483" y="0" type="line"/>
+ <point x="470" y="328" type="line"/>
+ </contour>
+ <contour>
+ <point x="1019" y="344" type="line"/>
+ <point x="1007" y="0" type="line"/>
+ <point x="1204" y="0" type="line"/>
+ <point x="1465" y="1082" type="line"/>
+ <point x="1156" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="858" y="1082" type="line"/>
+ <point x="661" y="1082" type="line"/>
+ <point x="727" y="699" type="line"/>
+ <point x="932" y="0" type="line"/>
+ <point x="1098" y="0" type="line"/>
+ <point x="1068" y="324" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/x.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/x.glif
new file mode 100644
index 00000000..0a5d8b6d
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/x.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="x" format="2">
+ <unicode hex="0078"/>
+ <advance width="1051"/>
+ <outline>
+ <contour>
+ <point x="370" y="1082" type="line"/>
+ <point x="30" y="1082" type="line"/>
+ <point x="321" y="555" type="line"/>
+ <point x="15" y="0" type="line"/>
+ <point x="355" y="0" type="line"/>
+ <point x="530" y="320" type="line"/>
+ <point x="707" y="0" type="line"/>
+ <point x="1046" y="0" type="line"/>
+ <point x="740" y="555" type="line"/>
+ <point x="1032" y="1082" type="line"/>
+ <point x="695" y="1082" type="line"/>
+ <point x="530" y="784" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/y.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/y.glif
new file mode 100644
index 00000000..01015dfb
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/y.glif
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="y" format="2">
+ <unicode hex="0079"/>
+ <advance width="1051"/>
+ <outline>
+ <contour>
+ <point x="428" y="127" type="line"/>
+ <point x="347" y="-82" type="line"/>
+ <point x="324" y="-143"/>
+ <point x="285" y="-176"/>
+ <point x="175" y="-176" type="curve" smooth="yes"/>
+ <point x="160" y="-176"/>
+ <point x="147" y="-176"/>
+ <point x="131" y="-176" type="curve"/>
+ <point x="131" y="-417" type="line"/>
+ <point x="187" y="-432"/>
+ <point x="205" y="-437"/>
+ <point x="267" y="-437" type="curve" smooth="yes"/>
+ <point x="508" y="-437"/>
+ <point x="583" y="-270"/>
+ <point x="621" y="-161" type="curve" smooth="yes"/>
+ <point x="1055" y="1082" type="line"/>
+ <point x="710" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="343" y="1082" type="line"/>
+ <point x="-2" y="1082" type="line"/>
+ <point x="384" y="-32" type="line"/>
+ <point x="600" y="-32" type="line"/>
+ <point x="562" y="325" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/z.glif b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/z.glif
new file mode 100644
index 00000000..ab555dcc
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/glyphs/z.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="z" format="2">
+ <unicode hex="007A"/>
+ <advance width="1051"/>
+ <outline>
+ <contour>
+ <point x="981" y="260" type="line"/>
+ <point x="149" y="260" type="line"/>
+ <point x="149" y="0" type="line"/>
+ <point x="981" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="969" y="900" type="line"/>
+ <point x="969" y="1082" type="line"/>
+ <point x="749" y="1082" type="line"/>
+ <point x="69" y="188" type="line"/>
+ <point x="69" y="0" type="line"/>
+ <point x="288" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="861" y="1082" type="line"/>
+ <point x="88" y="1082" type="line"/>
+ <point x="88" y="822" type="line"/>
+ <point x="861" y="822" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/layercontents.plist b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/layercontents.plist
new file mode 100644
index 00000000..03e5dde5
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/layercontents.plist
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <array>
+ <array>
+ <string>public.default</string>
+ <string>glyphs</string>
+ </array>
+ </array>
+</plist>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/lib.plist b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/lib.plist
new file mode 100644
index 00000000..5623ed1b
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/lib.plist
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>public.glyphOrder</key>
+ <array>
+ <string>space</string>
+ <string>A</string>
+ <string>B</string>
+ <string>C</string>
+ <string>D</string>
+ <string>E</string>
+ <string>F</string>
+ <string>G</string>
+ <string>H</string>
+ <string>I</string>
+ <string>J</string>
+ <string>K</string>
+ <string>L</string>
+ <string>M</string>
+ <string>N</string>
+ <string>O</string>
+ <string>P</string>
+ <string>Q</string>
+ <string>R</string>
+ <string>S</string>
+ <string>T</string>
+ <string>U</string>
+ <string>V</string>
+ <string>W</string>
+ <string>X</string>
+ <string>Y</string>
+ <string>Z</string>
+ <string>a</string>
+ <string>b</string>
+ <string>c</string>
+ <string>d</string>
+ <string>e</string>
+ <string>f</string>
+ <string>g</string>
+ <string>h</string>
+ <string>i</string>
+ <string>j</string>
+ <string>k</string>
+ <string>l</string>
+ <string>m</string>
+ <string>n</string>
+ <string>o</string>
+ <string>p</string>
+ <string>q</string>
+ <string>r</string>
+ <string>s</string>
+ <string>t</string>
+ <string>u</string>
+ <string>v</string>
+ <string>w</string>
+ <string>x</string>
+ <string>y</string>
+ <string>z</string>
+ </array>
+ </dict>
+</plist>
diff --git a/Tests/cu2qu/data/RobotoSubset-Bold.ufo/metainfo.plist b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/metainfo.plist
new file mode 100644
index 00000000..edfd6376
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Bold.ufo/metainfo.plist
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>creator</key>
+ <string>org.robofab.ufoLib</string>
+ <key>formatVersion</key>
+ <integer>3</integer>
+ </dict>
+</plist>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/fontinfo.plist b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/fontinfo.plist
new file mode 100644
index 00000000..28e318ea
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/fontinfo.plist
@@ -0,0 +1,214 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>ascender</key>
+ <integer>2146</integer>
+ <key>capHeight</key>
+ <integer>1456</integer>
+ <key>copyright</key>
+ <string>Copyright 2011 Google Inc. All Rights Reserved.</string>
+ <key>descender</key>
+ <integer>-555</integer>
+ <key>familyName</key>
+ <string>RobotoSubset</string>
+ <key>guidelines</key>
+ <array>
+ </array>
+ <key>italicAngle</key>
+ <integer>0</integer>
+ <key>openTypeHeadCreated</key>
+ <string>2008/09/12 12:29:34</string>
+ <key>openTypeHeadFlags</key>
+ <array>
+ <integer>0</integer>
+ <integer>1</integer>
+ <integer>3</integer>
+ <integer>4</integer>
+ </array>
+ <key>openTypeHeadLowestRecPPEM</key>
+ <integer>9</integer>
+ <key>openTypeHheaAscender</key>
+ <integer>1900</integer>
+ <key>openTypeHheaDescender</key>
+ <integer>-500</integer>
+ <key>openTypeHheaLineGap</key>
+ <integer>0</integer>
+ <key>openTypeNameDescription</key>
+ <string></string>
+ <key>openTypeNameDesigner</key>
+ <string></string>
+ <key>openTypeNameDesignerURL</key>
+ <string></string>
+ <key>openTypeNameLicense</key>
+ <string></string>
+ <key>openTypeNameLicenseURL</key>
+ <string></string>
+ <key>openTypeNameManufacturer</key>
+ <string></string>
+ <key>openTypeNameManufacturerURL</key>
+ <string></string>
+ <key>openTypeNameSampleText</key>
+ <string></string>
+ <key>openTypeOS2CodePageRanges</key>
+ <array>
+ <integer>0</integer>
+ <integer>1</integer>
+ <integer>2</integer>
+ <integer>3</integer>
+ <integer>4</integer>
+ <integer>7</integer>
+ <integer>8</integer>
+ <integer>29</integer>
+ </array>
+ <key>openTypeOS2FamilyClass</key>
+ <array>
+ <integer>0</integer>
+ <integer>0</integer>
+ </array>
+ <key>openTypeOS2Panose</key>
+ <array>
+ <integer>2</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ <integer>0</integer>
+ </array>
+ <key>openTypeOS2Selection</key>
+ <array>
+ </array>
+ <key>openTypeOS2StrikeoutPosition</key>
+ <integer>512</integer>
+ <key>openTypeOS2StrikeoutSize</key>
+ <integer>102</integer>
+ <key>openTypeOS2SubscriptXOffset</key>
+ <integer>0</integer>
+ <key>openTypeOS2SubscriptXSize</key>
+ <integer>1434</integer>
+ <key>openTypeOS2SubscriptYOffset</key>
+ <integer>287</integer>
+ <key>openTypeOS2SubscriptYSize</key>
+ <integer>1331</integer>
+ <key>openTypeOS2SuperscriptXOffset</key>
+ <integer>0</integer>
+ <key>openTypeOS2SuperscriptXSize</key>
+ <integer>1434</integer>
+ <key>openTypeOS2SuperscriptYOffset</key>
+ <integer>977</integer>
+ <key>openTypeOS2SuperscriptYSize</key>
+ <integer>1331</integer>
+ <key>openTypeOS2Type</key>
+ <array>
+ </array>
+ <key>openTypeOS2TypoAscender</key>
+ <integer>2146</integer>
+ <key>openTypeOS2TypoDescender</key>
+ <integer>-555</integer>
+ <key>openTypeOS2TypoLineGap</key>
+ <integer>0</integer>
+ <key>openTypeOS2UnicodeRanges</key>
+ <array>
+ <integer>0</integer>
+ <integer>1</integer>
+ <integer>2</integer>
+ <integer>3</integer>
+ <integer>4</integer>
+ <integer>5</integer>
+ <integer>6</integer>
+ <integer>7</integer>
+ <integer>9</integer>
+ <integer>11</integer>
+ <integer>29</integer>
+ <integer>30</integer>
+ <integer>31</integer>
+ <integer>32</integer>
+ <integer>33</integer>
+ <integer>34</integer>
+ <integer>35</integer>
+ <integer>36</integer>
+ <integer>37</integer>
+ <integer>38</integer>
+ <integer>40</integer>
+ <integer>45</integer>
+ <integer>60</integer>
+ <integer>62</integer>
+ <integer>64</integer>
+ <integer>69</integer>
+ </array>
+ <key>openTypeOS2VendorID</key>
+ <string>GOOG</string>
+ <key>openTypeOS2WeightClass</key>
+ <integer>400</integer>
+ <key>openTypeOS2WidthClass</key>
+ <integer>5</integer>
+ <key>openTypeOS2WinAscent</key>
+ <integer>2146</integer>
+ <key>openTypeOS2WinDescent</key>
+ <integer>555</integer>
+ <key>postscriptBlueFuzz</key>
+ <integer>1</integer>
+ <key>postscriptBlueScale</key>
+ <real>0.039625</real>
+ <key>postscriptBlueShift</key>
+ <integer>7</integer>
+ <key>postscriptBlueValues</key>
+ <array>
+ <integer>-20</integer>
+ <integer>0</integer>
+ <integer>1082</integer>
+ <integer>1102</integer>
+ <integer>1456</integer>
+ <integer>1476</integer>
+ </array>
+ <key>postscriptDefaultCharacter</key>
+ <string>space</string>
+ <key>postscriptFamilyBlues</key>
+ <array>
+ </array>
+ <key>postscriptFamilyOtherBlues</key>
+ <array>
+ </array>
+ <key>postscriptForceBold</key>
+ <false/>
+ <key>postscriptIsFixedPitch</key>
+ <false/>
+ <key>postscriptOtherBlues</key>
+ <array>
+ <integer>-436</integer>
+ <integer>-416</integer>
+ </array>
+ <key>postscriptStemSnapH</key>
+ <array>
+ <integer>154</integer>
+ </array>
+ <key>postscriptStemSnapV</key>
+ <array>
+ <integer>196</integer>
+ </array>
+ <key>postscriptUnderlinePosition</key>
+ <integer>-150</integer>
+ <key>postscriptUnderlineThickness</key>
+ <integer>100</integer>
+ <key>postscriptUniqueID</key>
+ <integer>-1</integer>
+ <key>styleName</key>
+ <string>Regular</string>
+ <key>trademark</key>
+ <string></string>
+ <key>unitsPerEm</key>
+ <integer>2048</integer>
+ <key>versionMajor</key>
+ <integer>1</integer>
+ <key>versionMinor</key>
+ <integer>0</integer>
+ <key>xHeight</key>
+ <integer>1082</integer>
+ <key>year</key>
+ <integer>2017</integer>
+ </dict>
+</plist>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/A_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/A_.glif
new file mode 100644
index 00000000..8f070f21
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/A_.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="A" format="2">
+ <unicode hex="0041"/>
+ <advance width="1349"/>
+ <outline>
+ <contour>
+ <point x="718" y="1320" type="line"/>
+ <point x="720" y="1456" type="line"/>
+ <point x="585" y="1456" type="line"/>
+ <point x="28" y="0" type="line"/>
+ <point x="241" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1110" y="0" type="line"/>
+ <point x="1323" y="0" type="line"/>
+ <point x="765" y="1456" type="line"/>
+ <point x="630" y="1456" type="line"/>
+ <point x="632" y="1320" type="line"/>
+ </contour>
+ <contour>
+ <point x="1099" y="543" type="line"/>
+ <point x="271" y="543" type="line"/>
+ <point x="271" y="376" type="line"/>
+ <point x="1099" y="376" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/B_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/B_.glif
new file mode 100644
index 00000000..2a5194a8
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/B_.glif
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="B" format="2">
+ <unicode hex="0042"/>
+ <advance width="1280"/>
+ <outline>
+ <contour>
+ <point x="688" y="678" type="line"/>
+ <point x="755" y="726" type="line"/>
+ <point x="976" y="752"/>
+ <point x="1128" y="887"/>
+ <point x="1128" y="1067" type="curve"/>
+ <point x="1128" y="1339"/>
+ <point x="951" y="1456"/>
+ <point x="653" y="1456" type="curve"/>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="374" y="0" type="line"/>
+ <point x="374" y="1289" type="line"/>
+ <point x="653" y="1289" type="line"/>
+ <point x="834" y="1289"/>
+ <point x="920" y="1224"/>
+ <point x="920" y="1068" type="curve"/>
+ <point x="920" y="927"/>
+ <point x="812" y="841"/>
+ <point x="657" y="841" type="curve"/>
+ <point x="327" y="841" type="line"/>
+ <point x="329" y="678" type="line"/>
+ </contour>
+ <contour>
+ <point x="679" y="0" type="line"/>
+ <point x="973" y="0"/>
+ <point x="1164" y="148"/>
+ <point x="1164" y="422" type="curve" smooth="yes"/>
+ <point x="1164" y="610"/>
+ <point x="1048" y="764"/>
+ <point x="835" y="782" type="curve"/>
+ <point x="790" y="841" type="line"/>
+ <point x="411" y="841" type="line"/>
+ <point x="409" y="678" type="line"/>
+ <point x="688" y="678" type="line" smooth="yes"/>
+ <point x="877" y="678"/>
+ <point x="956" y="581"/>
+ <point x="956" y="420" type="curve" smooth="yes"/>
+ <point x="956" y="265"/>
+ <point x="855" y="166"/>
+ <point x="679" y="166" type="curve" smooth="yes"/>
+ <point x="364" y="166" type="line"/>
+ <point x="244" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/C_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/C_.glif
new file mode 100644
index 00000000..eaabceef
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/C_.glif
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="C" format="2">
+ <unicode hex="0043"/>
+ <advance width="1334"/>
+ <outline>
+ <contour>
+ <point x="1038" y="464" type="line"/>
+ <point x="1005" y="266"/>
+ <point x="933" y="146"/>
+ <point x="690" y="146" type="curve" smooth="yes"/>
+ <point x="434" y="146"/>
+ <point x="325" y="378"/>
+ <point x="325" y="658" type="curve" smooth="yes"/>
+ <point x="325" y="799" type="line" smooth="yes"/>
+ <point x="325" y="1104"/>
+ <point x="454" y="1309"/>
+ <point x="709" y="1309" type="curve" smooth="yes"/>
+ <point x="928" y="1309"/>
+ <point x="1009" y="1184"/>
+ <point x="1038" y="987" type="curve"/>
+ <point x="1246" y="987" type="line"/>
+ <point x="1216" y="1272"/>
+ <point x="1043" y="1476"/>
+ <point x="709" y="1476" type="curve" smooth="yes"/>
+ <point x="344" y="1476"/>
+ <point x="117" y="1207"/>
+ <point x="117" y="797" type="curve"/>
+ <point x="117" y="658" type="line"/>
+ <point x="117" y="248"/>
+ <point x="345" y="-20"/>
+ <point x="690" y="-20" type="curve" smooth="yes"/>
+ <point x="1046" y="-20"/>
+ <point x="1215" y="192"/>
+ <point x="1246" y="464" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/D_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/D_.glif
new file mode 100644
index 00000000..b89cff9a
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/D_.glif
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="D" format="2">
+ <unicode hex="0044"/>
+ <advance width="1344"/>
+ <outline>
+ <contour>
+ <point x="559" y="0" type="line"/>
+ <point x="969" y="0"/>
+ <point x="1225" y="263"/>
+ <point x="1225" y="688" type="curve"/>
+ <point x="1225" y="767" type="line"/>
+ <point x="1225" y="1192"/>
+ <point x="965" y="1456"/>
+ <point x="578" y="1456" type="curve"/>
+ <point x="254" y="1456" type="line"/>
+ <point x="254" y="1289" type="line"/>
+ <point x="578" y="1289" type="line"/>
+ <point x="863" y="1289"/>
+ <point x="1019" y="1102"/>
+ <point x="1019" y="769" type="curve"/>
+ <point x="1019" y="688" type="line" smooth="yes"/>
+ <point x="1019" y="371"/>
+ <point x="869" y="166"/>
+ <point x="559" y="166" type="curve"/>
+ <point x="262" y="166" type="line"/>
+ <point x="260" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="374" y="1456" type="line"/>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="374" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/E_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/E_.glif
new file mode 100644
index 00000000..14f1ad9a
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/E_.glif
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="E" format="2">
+ <unicode hex="0045"/>
+ <advance width="1164"/>
+ <outline>
+ <contour>
+ <point x="1095" y="166" type="line"/>
+ <point x="335" y="166" type="line"/>
+ <point x="335" y="0" type="line"/>
+ <point x="1095" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="374" y="1456" type="line"/>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="374" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="993" y="835" type="line"/>
+ <point x="335" y="835" type="line"/>
+ <point x="335" y="669" type="line"/>
+ <point x="993" y="669" type="line"/>
+ </contour>
+ <contour>
+ <point x="1084" y="1456" type="line"/>
+ <point x="335" y="1456" type="line"/>
+ <point x="335" y="1289" type="line"/>
+ <point x="1084" y="1289" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/F_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/F_.glif
new file mode 100644
index 00000000..77ffdb85
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/F_.glif
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="F" format="2">
+ <unicode hex="0046"/>
+ <advance width="1128"/>
+ <outline>
+ <contour>
+ <point x="374" y="1456" type="line"/>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="374" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="969" y="803" type="line"/>
+ <point x="332" y="803" type="line"/>
+ <point x="332" y="637" type="line"/>
+ <point x="969" y="637" type="line"/>
+ </contour>
+ <contour>
+ <point x="1068" y="1456" type="line"/>
+ <point x="332" y="1456" type="line"/>
+ <point x="332" y="1289" type="line"/>
+ <point x="1068" y="1289" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/G_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/G_.glif
new file mode 100644
index 00000000..dd03d178
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/G_.glif
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="G" format="2">
+ <unicode hex="0047"/>
+ <advance width="1394"/>
+ <outline>
+ <contour>
+ <point x="1247" y="731" type="line"/>
+ <point x="715" y="731" type="line"/>
+ <point x="715" y="566" type="line"/>
+ <point x="1040" y="566" type="line"/>
+ <point x="1040" y="248" type="line"/>
+ <point x="1002" y="206"/>
+ <point x="934" y="146"/>
+ <point x="731" y="146" type="curve" smooth="yes"/>
+ <point x="489" y="146"/>
+ <point x="326" y="341"/>
+ <point x="326" y="676" type="curve" smooth="yes"/>
+ <point x="326" y="781" type="line" smooth="yes"/>
+ <point x="326" y="1108"/>
+ <point x="440" y="1309"/>
+ <point x="708" y="1309" type="curve" smooth="yes"/>
+ <point x="922" y="1309"/>
+ <point x="1012" y="1181"/>
+ <point x="1039" y="1027" type="curve"/>
+ <point x="1247" y="1027" type="line"/>
+ <point x="1209" y="1287"/>
+ <point x="1042" y="1476"/>
+ <point x="707" y="1476" type="curve" smooth="yes"/>
+ <point x="325" y="1476"/>
+ <point x="117" y="1219"/>
+ <point x="117" y="779" type="curve" smooth="yes"/>
+ <point x="117" y="676" type="line" smooth="yes"/>
+ <point x="117" y="236"/>
+ <point x="373" y="-20"/>
+ <point x="730" y="-20" type="curve" smooth="yes"/>
+ <point x="1061" y="-20"/>
+ <point x="1191" y="114"/>
+ <point x="1247" y="195" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/H_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/H_.glif
new file mode 100644
index 00000000..fa6876c1
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/H_.glif
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="H" format="2">
+ <unicode hex="0048"/>
+ <advance width="1463"/>
+ <outline>
+ <contour>
+ <point x="1110" y="835" type="line"/>
+ <point x="344" y="835" type="line"/>
+ <point x="344" y="669" type="line"/>
+ <point x="1110" y="669" type="line"/>
+ </contour>
+ <contour>
+ <point x="374" y="1456" type="line"/>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="374" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1294" y="1456" type="line"/>
+ <point x="1086" y="1456" type="line"/>
+ <point x="1086" y="0" type="line"/>
+ <point x="1294" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/I_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/I_.glif
new file mode 100644
index 00000000..cdf1ebf3
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/I_.glif
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="I" format="2">
+ <unicode hex="0049"/>
+ <advance width="560"/>
+ <outline>
+ <contour>
+ <point x="385" y="1456" type="line"/>
+ <point x="177" y="1456" type="line"/>
+ <point x="177" y="0" type="line"/>
+ <point x="385" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/J_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/J_.glif
new file mode 100644
index 00000000..34346b78
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/J_.glif
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="J" format="2">
+ <unicode hex="004A"/>
+ <advance width="1131"/>
+ <outline>
+ <contour>
+ <point x="769" y="424" type="line"/>
+ <point x="769" y="242"/>
+ <point x="660" y="146"/>
+ <point x="513" y="146" type="curve" smooth="yes"/>
+ <point x="366" y="146"/>
+ <point x="257" y="224"/>
+ <point x="257" y="403" type="curve"/>
+ <point x="49" y="403" type="line"/>
+ <point x="49" y="116"/>
+ <point x="244" y="-20"/>
+ <point x="513" y="-20" type="curve" smooth="yes"/>
+ <point x="784" y="-20"/>
+ <point x="977" y="136"/>
+ <point x="977" y="424" type="curve"/>
+ <point x="977" y="1456" type="line"/>
+ <point x="769" y="1456" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/K_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/K_.glif
new file mode 100644
index 00000000..2c271240
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/K_.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="K" format="2">
+ <unicode hex="004B"/>
+ <advance width="1283"/>
+ <outline>
+ <contour>
+ <point x="374" y="1456" type="line"/>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="374" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1248" y="1456" type="line"/>
+ <point x="999" y="1456" type="line"/>
+ <point x="523" y="918" type="line"/>
+ <point x="267" y="632" type="line"/>
+ <point x="303" y="415" type="line"/>
+ <point x="648" y="775" type="line"/>
+ </contour>
+ <contour>
+ <point x="1044" y="0" type="line"/>
+ <point x="1292" y="0" type="line"/>
+ <point x="645" y="866" type="line"/>
+ <point x="521" y="703" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/L_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/L_.glif
new file mode 100644
index 00000000..85d578dd
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/L_.glif
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="L" format="2">
+ <unicode hex="004C"/>
+ <advance width="1108"/>
+ <outline>
+ <contour>
+ <point x="1058" y="166" type="line"/>
+ <point x="335" y="166" type="line"/>
+ <point x="335" y="0" type="line"/>
+ <point x="1058" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="374" y="1456" type="line"/>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="374" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/M_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/M_.glif
new file mode 100644
index 00000000..f67a855f
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/M_.glif
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="M" format="2">
+ <unicode hex="004D"/>
+ <advance width="1792"/>
+ <outline>
+ <contour>
+ <point x="231" y="1456" type="line"/>
+ <point x="816" y="0" type="line"/>
+ <point x="974" y="0" type="line"/>
+ <point x="1560" y="1456" type="line"/>
+ <point x="1358" y="1456" type="line"/>
+ <point x="896" y="285" type="line"/>
+ <point x="433" y="1456" type="line"/>
+ </contour>
+ <contour>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="373" y="0" type="line"/>
+ <point x="373" y="556" type="line"/>
+ <point x="343" y="1456" type="line"/>
+ </contour>
+ <contour>
+ <point x="1448" y="1456" type="line"/>
+ <point x="1418" y="556" type="line"/>
+ <point x="1418" y="0" type="line"/>
+ <point x="1625" y="0" type="line"/>
+ <point x="1625" y="1456" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/N_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/N_.glif
new file mode 100644
index 00000000..70b3869b
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/N_.glif
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="N" format="2">
+ <unicode hex="004E"/>
+ <advance width="1462"/>
+ <outline>
+ <contour>
+ <point x="1293" y="1456" type="line"/>
+ <point x="1087" y="1456" type="line"/>
+ <point x="1087" y="350" type="line"/>
+ <point x="374" y="1456" type="line"/>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="374" y="0" type="line"/>
+ <point x="374" y="1102" type="line"/>
+ <point x="1084" y="0" type="line"/>
+ <point x="1293" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/O_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/O_.glif
new file mode 100644
index 00000000..6d87c263
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/O_.glif
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="O" format="2">
+ <unicode hex="004F"/>
+ <advance width="1414"/>
+ <outline>
+ <contour>
+ <point x="1296" y="768" type="line"/>
+ <point x="1296" y="1209"/>
+ <point x="1063" y="1476"/>
+ <point x="706" y="1476" type="curve" smooth="yes"/>
+ <point x="360" y="1476"/>
+ <point x="117" y="1209"/>
+ <point x="117" y="768" type="curve"/>
+ <point x="117" y="687" type="line"/>
+ <point x="117" y="246"/>
+ <point x="362" y="-20"/>
+ <point x="708" y="-20" type="curve" smooth="yes"/>
+ <point x="1065" y="-20"/>
+ <point x="1296" y="246"/>
+ <point x="1296" y="687" type="curve"/>
+ </contour>
+ <contour>
+ <point x="1090" y="687" type="line"/>
+ <point x="1090" y="338"/>
+ <point x="952" y="153"/>
+ <point x="708" y="153" type="curve" smooth="yes"/>
+ <point x="478" y="153"/>
+ <point x="323" y="338"/>
+ <point x="323" y="687" type="curve" smooth="yes"/>
+ <point x="323" y="770" type="line" smooth="yes"/>
+ <point x="323" y="1117"/>
+ <point x="476" y="1302"/>
+ <point x="706" y="1302" type="curve" smooth="yes"/>
+ <point x="948" y="1302"/>
+ <point x="1090" y="1117"/>
+ <point x="1090" y="770" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/P_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/P_.glif
new file mode 100644
index 00000000..81622d16
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/P_.glif
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="P" format="2">
+ <unicode hex="0050"/>
+ <advance width="1299"/>
+ <outline>
+ <contour>
+ <point x="712" y="567" type="line"/>
+ <point x="1044" y="567"/>
+ <point x="1227" y="727"/>
+ <point x="1227" y="1010" type="curve"/>
+ <point x="1227" y="1269"/>
+ <point x="1044" y="1456"/>
+ <point x="712" y="1456" type="curve"/>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="374" y="0" type="line"/>
+ <point x="374" y="1289" type="line"/>
+ <point x="712" y="1289" type="line"/>
+ <point x="930" y="1289"/>
+ <point x="1019" y="1153"/>
+ <point x="1019" y="1008" type="curve"/>
+ <point x="1019" y="846"/>
+ <point x="930" y="733"/>
+ <point x="712" y="733" type="curve"/>
+ <point x="329" y="733" type="line"/>
+ <point x="329" y="567" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Q_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Q_.glif
new file mode 100644
index 00000000..9dfc7f66
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Q_.glif
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="Q" format="2">
+ <unicode hex="0051"/>
+ <advance width="1414"/>
+ <outline>
+ <contour>
+ <point x="918" y="176" type="line"/>
+ <point x="785" y="45" type="line"/>
+ <point x="1156" y="-245" type="line"/>
+ <point x="1297" y="-117" type="line"/>
+ </contour>
+ <contour>
+ <point x="1287" y="768" type="line"/>
+ <point x="1287" y="1209"/>
+ <point x="1054" y="1476"/>
+ <point x="696" y="1476" type="curve" smooth="yes"/>
+ <point x="350" y="1476"/>
+ <point x="107" y="1209"/>
+ <point x="107" y="768" type="curve"/>
+ <point x="107" y="687" type="line"/>
+ <point x="107" y="246"/>
+ <point x="352" y="-20"/>
+ <point x="698" y="-20" type="curve" smooth="yes"/>
+ <point x="1056" y="-20"/>
+ <point x="1287" y="246"/>
+ <point x="1287" y="687" type="curve"/>
+ </contour>
+ <contour>
+ <point x="1080" y="687" type="line"/>
+ <point x="1080" y="338"/>
+ <point x="942" y="153"/>
+ <point x="698" y="153" type="curve" smooth="yes"/>
+ <point x="469" y="153"/>
+ <point x="314" y="338"/>
+ <point x="314" y="687" type="curve" smooth="yes"/>
+ <point x="314" y="770" type="line" smooth="yes"/>
+ <point x="314" y="1117"/>
+ <point x="467" y="1302"/>
+ <point x="696" y="1302" type="curve" smooth="yes"/>
+ <point x="939" y="1302"/>
+ <point x="1080" y="1117"/>
+ <point x="1080" y="770" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/R_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/R_.glif
new file mode 100644
index 00000000..8d35190b
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/R_.glif
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="R" format="2">
+ <unicode hex="0052"/>
+ <advance width="1253"/>
+ <outline>
+ <contour>
+ <point x="166" y="1456" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="374" y="0" type="line"/>
+ <point x="374" y="1289" type="line"/>
+ <point x="650" y="1289" type="line" smooth="yes"/>
+ <point x="868" y="1289"/>
+ <point x="956" y="1180"/>
+ <point x="956" y="1017" type="curve" smooth="yes"/>
+ <point x="956" y="869"/>
+ <point x="854" y="753"/>
+ <point x="651" y="753" type="curve" smooth="yes"/>
+ <point x="327" y="753" type="line"/>
+ <point x="329" y="587" type="line"/>
+ <point x="770" y="587" type="line"/>
+ <point x="827" y="609" type="line"/>
+ <point x="1039" y="666"/>
+ <point x="1164" y="818"/>
+ <point x="1164" y="1017" type="curve" smooth="yes"/>
+ <point x="1164" y="1303"/>
+ <point x="983" y="1456"/>
+ <point x="650" y="1456" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="1006" y="0" type="line"/>
+ <point x="1229" y="0" type="line"/>
+ <point x="1229" y="12" type="line"/>
+ <point x="874" y="662" type="line"/>
+ <point x="657" y="662" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/S_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/S_.glif
new file mode 100644
index 00000000..8bc81cea
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/S_.glif
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="S" format="2">
+ <unicode hex="0053"/>
+ <advance width="1216"/>
+ <outline>
+ <contour>
+ <point x="931" y="370" type="curve" smooth="yes"/>
+ <point x="931" y="232"/>
+ <point x="826" y="146"/>
+ <point x="631" y="146" type="curve" smooth="yes"/>
+ <point x="446" y="146"/>
+ <point x="287" y="232"/>
+ <point x="287" y="423" type="curve"/>
+ <point x="79" y="423" type="line"/>
+ <point x="79" y="136"/>
+ <point x="360" y="-20"/>
+ <point x="631" y="-20" type="curve" smooth="yes"/>
+ <point x="942" y="-20"/>
+ <point x="1140" y="135"/>
+ <point x="1140" y="372" type="curve"/>
+ <point x="1140" y="596"/>
+ <point x="998" y="727"/>
+ <point x="663" y="822" type="curve"/>
+ <point x="435" y="886"/>
+ <point x="334" y="959"/>
+ <point x="334" y="1079" type="curve"/>
+ <point x="334" y="1212"/>
+ <point x="423" y="1309"/>
+ <point x="621" y="1309" type="curve" smooth="yes"/>
+ <point x="834" y="1309"/>
+ <point x="930" y="1194"/>
+ <point x="930" y="1035" type="curve"/>
+ <point x="1138" y="1035" type="line"/>
+ <point x="1138" y="1260"/>
+ <point x="955" y="1476"/>
+ <point x="621" y="1476" type="curve" smooth="yes"/>
+ <point x="320" y="1476"/>
+ <point x="125" y="1303"/>
+ <point x="125" y="1076" type="curve"/>
+ <point x="125" y="851"/>
+ <point x="309" y="728"/>
+ <point x="596" y="644" type="curve"/>
+ <point x="865" y="565"/>
+ <point x="931" y="502"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/T_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/T_.glif
new file mode 100644
index 00000000..dff26561
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/T_.glif
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="T" format="2">
+ <unicode hex="0054"/>
+ <advance width="1222"/>
+ <outline>
+ <contour>
+ <point x="715" y="1456" type="line"/>
+ <point x="509" y="1456" type="line"/>
+ <point x="509" y="0" type="line"/>
+ <point x="715" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1176" y="1456" type="line"/>
+ <point x="49" y="1456" type="line"/>
+ <point x="49" y="1289" type="line"/>
+ <point x="1176" y="1289" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/U_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/U_.glif
new file mode 100644
index 00000000..ee592bc5
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/U_.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="U" format="2">
+ <unicode hex="0055"/>
+ <advance width="1324"/>
+ <outline>
+ <contour>
+ <point x="988" y="1456" type="line"/>
+ <point x="988" y="471" type="line" smooth="yes"/>
+ <point x="988" y="248"/>
+ <point x="860" y="146"/>
+ <point x="664" y="146" type="curve" smooth="yes"/>
+ <point x="470" y="146"/>
+ <point x="341" y="248"/>
+ <point x="341" y="471" type="curve" smooth="yes"/>
+ <point x="341" y="1456" type="line"/>
+ <point x="135" y="1456" type="line"/>
+ <point x="135" y="471" type="line"/>
+ <point x="135" y="143"/>
+ <point x="366" y="-20"/>
+ <point x="664" y="-20" type="curve" smooth="yes"/>
+ <point x="946" y="-20"/>
+ <point x="1196" y="143"/>
+ <point x="1196" y="471" type="curve"/>
+ <point x="1196" y="1456" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/V_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/V_.glif
new file mode 100644
index 00000000..232cc55e
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/V_.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="V" format="2">
+ <unicode hex="0056"/>
+ <advance width="1313"/>
+ <outline>
+ <contour>
+ <point x="639" y="228" type="line"/>
+ <point x="588" y="0" type="line"/>
+ <point x="748" y="0" type="line"/>
+ <point x="1287" y="1456" type="line"/>
+ <point x="1061" y="1456" type="line"/>
+ </contour>
+ <contour>
+ <point x="254" y="1456" type="line"/>
+ <point x="28" y="1456" type="line"/>
+ <point x="566" y="0" type="line"/>
+ <point x="726" y="0" type="line"/>
+ <point x="672" y="228" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/W_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/W_.glif
new file mode 100644
index 00000000..4c1b0171
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/W_.glif
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="W" format="2">
+ <unicode hex="0057"/>
+ <advance width="1813"/>
+ <outline>
+ <contour>
+ <point x="552" y="450" type="line"/>
+ <point x="448" y="0" type="line"/>
+ <point x="597" y="0" type="line"/>
+ <point x="902" y="1048" type="line"/>
+ <point x="984" y="1456" type="line"/>
+ <point x="834" y="1456" type="line"/>
+ </contour>
+ <contour>
+ <point x="268" y="1456" type="line"/>
+ <point x="61" y="1456" type="line"/>
+ <point x="409" y="0" type="line"/>
+ <point x="556" y="0" type="line"/>
+ <point x="490" y="471" type="line"/>
+ </contour>
+ <contour>
+ <point x="1346" y="472" type="line"/>
+ <point x="1276" y="0" type="line"/>
+ <point x="1423" y="0" type="line"/>
+ <point x="1771" y="1456" type="line"/>
+ <point x="1563" y="1456" type="line"/>
+ </contour>
+ <contour>
+ <point x="1007" y="1456" type="line"/>
+ <point x="860" y="1456" type="line"/>
+ <point x="942" y="1048" type="line"/>
+ <point x="1235" y="0" type="line"/>
+ <point x="1384" y="0" type="line"/>
+ <point x="1281" y="450" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/X_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/X_.glif
new file mode 100644
index 00000000..a8547b3c
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/X_.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="X" format="2">
+ <unicode hex="0058"/>
+ <advance width="1291"/>
+ <outline>
+ <contour>
+ <point x="311" y="1456" type="line"/>
+ <point x="68" y="1456" type="line"/>
+ <point x="524" y="734" type="line"/>
+ <point x="58" y="0" type="line"/>
+ <point x="303" y="0" type="line"/>
+ <point x="648" y="557" type="line"/>
+ <point x="992" y="0" type="line"/>
+ <point x="1237" y="0" type="line"/>
+ <point x="772" y="734" type="line"/>
+ <point x="1227" y="1456" type="line"/>
+ <point x="984" y="1456" type="line"/>
+ <point x="648" y="908" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Y_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Y_.glif
new file mode 100644
index 00000000..6d48150f
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Y_.glif
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="Y" format="2">
+ <unicode hex="0059"/>
+ <advance width="1231"/>
+ <outline>
+ <contour>
+ <point x="250" y="1456" type="line"/>
+ <point x="13" y="1456" type="line"/>
+ <point x="510" y="543" type="line"/>
+ <point x="510" y="0" type="line"/>
+ <point x="718" y="0" type="line"/>
+ <point x="718" y="543" type="line"/>
+ <point x="1215" y="1456" type="line"/>
+ <point x="979" y="1456" type="line"/>
+ <point x="614" y="736" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Z_.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Z_.glif
new file mode 100644
index 00000000..6b5dfc49
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/Z_.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="Z" format="2">
+ <unicode hex="005A"/>
+ <advance width="1227"/>
+ <outline>
+ <contour>
+ <point x="1148" y="166" type="line"/>
+ <point x="165" y="166" type="line"/>
+ <point x="165" y="0" type="line"/>
+ <point x="1148" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1116" y="1307" type="line"/>
+ <point x="1116" y="1456" type="line"/>
+ <point x="987" y="1456" type="line"/>
+ <point x="86" y="153" type="line"/>
+ <point x="86" y="0" type="line"/>
+ <point x="214" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="1028" y="1456" type="line"/>
+ <point x="96" y="1456" type="line"/>
+ <point x="96" y="1289" type="line"/>
+ <point x="1028" y="1289" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/a.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/a.glif
new file mode 100644
index 00000000..7d8a2cdb
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/a.glif
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="a" format="2">
+ <unicode hex="0061"/>
+ <advance width="1118"/>
+ <outline>
+ <contour>
+ <point x="771" y="183" type="line"/>
+ <point x="771" y="123"/>
+ <point x="782" y="41"/>
+ <point x="802" y="0" type="curve"/>
+ <point x="1010" y="0" type="line"/>
+ <point x="1010" y="17" type="line"/>
+ <point x="984" y="76"/>
+ <point x="971" y="166"/>
+ <point x="971" y="238" type="curve" smooth="yes"/>
+ <point x="971" y="738" type="line"/>
+ <point x="971" y="981"/>
+ <point x="803" y="1102"/>
+ <point x="566" y="1102" type="curve" smooth="yes"/>
+ <point x="301" y="1102"/>
+ <point x="133" y="935"/>
+ <point x="133" y="782" type="curve"/>
+ <point x="333" y="782" type="line"/>
+ <point x="333" y="867"/>
+ <point x="421" y="945"/>
+ <point x="554" y="945" type="curve" smooth="yes"/>
+ <point x="697" y="945"/>
+ <point x="771" y="864"/>
+ <point x="771" y="740" type="curve"/>
+ </contour>
+ <contour>
+ <point x="804" y="661" type="line"/>
+ <point x="596" y="661" type="line"/>
+ <point x="301" y="661"/>
+ <point x="111" y="539"/>
+ <point x="111" y="303" type="curve" smooth="yes"/>
+ <point x="111" y="123"/>
+ <point x="257" y="-20"/>
+ <point x="480" y="-20" type="curve" smooth="yes"/>
+ <point x="711" y="-20"/>
+ <point x="857" y="170"/>
+ <point x="874" y="277" type="curve"/>
+ <point x="789" y="370" type="line"/>
+ <point x="789" y="279"/>
+ <point x="673" y="151"/>
+ <point x="510" y="151" type="curve" smooth="yes"/>
+ <point x="377" y="151"/>
+ <point x="311" y="230"/>
+ <point x="311" y="331" type="curve" smooth="yes"/>
+ <point x="311" y="462"/>
+ <point x="425" y="524"/>
+ <point x="629" y="524" type="curve"/>
+ <point x="806" y="524" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/b.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/b.glif
new file mode 100644
index 00000000..f009d99e
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/b.glif
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="b" format="2">
+ <unicode hex="0062"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="137" y="1536" type="line"/>
+ <point x="137" y="0" type="line"/>
+ <point x="320" y="0" type="line"/>
+ <point x="337" y="210" type="line"/>
+ <point x="337" y="1536" type="line"/>
+ </contour>
+ <contour>
+ <point x="1063" y="550" type="line"/>
+ <point x="1063" y="879"/>
+ <point x="912" y="1102"/>
+ <point x="639" y="1102" type="curve" smooth="yes"/>
+ <point x="362" y="1102"/>
+ <point x="230" y="901"/>
+ <point x="200" y="572" type="curve"/>
+ <point x="200" y="510" type="line"/>
+ <point x="230" y="181"/>
+ <point x="362" y="-20"/>
+ <point x="641" y="-20" type="curve" smooth="yes"/>
+ <point x="910" y="-20"/>
+ <point x="1063" y="214"/>
+ <point x="1063" y="529" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="863" y="529" type="line"/>
+ <point x="863" y="320"/>
+ <point x="785" y="146"/>
+ <point x="590" y="146" type="curve" smooth="yes"/>
+ <point x="411" y="146"/>
+ <point x="327" y="287"/>
+ <point x="296" y="426" type="curve"/>
+ <point x="296" y="655" type="line"/>
+ <point x="326" y="803"/>
+ <point x="411" y="937"/>
+ <point x="588" y="937" type="curve" smooth="yes"/>
+ <point x="794" y="937"/>
+ <point x="863" y="759"/>
+ <point x="863" y="550" type="curve" smooth="yes"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/c.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/c.glif
new file mode 100644
index 00000000..727fef49
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/c.glif
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="c" format="2">
+ <unicode hex="0063"/>
+ <advance width="1076"/>
+ <outline>
+ <contour>
+ <point x="578" y="140" type="curve" smooth="yes"/>
+ <point x="352" y="140"/>
+ <point x="292" y="337"/>
+ <point x="292" y="520" type="curve" smooth="yes"/>
+ <point x="292" y="562" type="line" smooth="yes"/>
+ <point x="292" y="743"/>
+ <point x="354" y="942"/>
+ <point x="578" y="942" type="curve"/>
+ <point x="723" y="942"/>
+ <point x="813" y="835"/>
+ <point x="823" y="709" type="curve"/>
+ <point x="1012" y="709" type="line"/>
+ <point x="1002" y="929"/>
+ <point x="836" y="1102"/>
+ <point x="578" y="1102" type="curve" smooth="yes"/>
+ <point x="248" y="1102"/>
+ <point x="92" y="850"/>
+ <point x="92" y="562" type="curve" smooth="yes"/>
+ <point x="92" y="520" type="line" smooth="yes"/>
+ <point x="92" y="232"/>
+ <point x="247" y="-20"/>
+ <point x="578" y="-20" type="curve"/>
+ <point x="809" y="-20"/>
+ <point x="1002" y="153"/>
+ <point x="1012" y="343" type="curve"/>
+ <point x="823" y="343" type="line"/>
+ <point x="813" y="229"/>
+ <point x="706" y="140"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/contents.plist b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/contents.plist
new file mode 100644
index 00000000..431aca78
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/contents.plist
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>A</key>
+ <string>A_.glif</string>
+ <key>B</key>
+ <string>B_.glif</string>
+ <key>C</key>
+ <string>C_.glif</string>
+ <key>D</key>
+ <string>D_.glif</string>
+ <key>E</key>
+ <string>E_.glif</string>
+ <key>F</key>
+ <string>F_.glif</string>
+ <key>G</key>
+ <string>G_.glif</string>
+ <key>H</key>
+ <string>H_.glif</string>
+ <key>I</key>
+ <string>I_.glif</string>
+ <key>J</key>
+ <string>J_.glif</string>
+ <key>K</key>
+ <string>K_.glif</string>
+ <key>L</key>
+ <string>L_.glif</string>
+ <key>M</key>
+ <string>M_.glif</string>
+ <key>N</key>
+ <string>N_.glif</string>
+ <key>O</key>
+ <string>O_.glif</string>
+ <key>P</key>
+ <string>P_.glif</string>
+ <key>Q</key>
+ <string>Q_.glif</string>
+ <key>R</key>
+ <string>R_.glif</string>
+ <key>S</key>
+ <string>S_.glif</string>
+ <key>T</key>
+ <string>T_.glif</string>
+ <key>U</key>
+ <string>U_.glif</string>
+ <key>V</key>
+ <string>V_.glif</string>
+ <key>W</key>
+ <string>W_.glif</string>
+ <key>X</key>
+ <string>X_.glif</string>
+ <key>Y</key>
+ <string>Y_.glif</string>
+ <key>Z</key>
+ <string>Z_.glif</string>
+ <key>a</key>
+ <string>a.glif</string>
+ <key>b</key>
+ <string>b.glif</string>
+ <key>c</key>
+ <string>c.glif</string>
+ <key>d</key>
+ <string>d.glif</string>
+ <key>e</key>
+ <string>e.glif</string>
+ <key>f</key>
+ <string>f.glif</string>
+ <key>g</key>
+ <string>g.glif</string>
+ <key>h</key>
+ <string>h.glif</string>
+ <key>i</key>
+ <string>i.glif</string>
+ <key>j</key>
+ <string>j.glif</string>
+ <key>k</key>
+ <string>k.glif</string>
+ <key>l</key>
+ <string>l.glif</string>
+ <key>m</key>
+ <string>m.glif</string>
+ <key>n</key>
+ <string>n.glif</string>
+ <key>o</key>
+ <string>o.glif</string>
+ <key>p</key>
+ <string>p.glif</string>
+ <key>q</key>
+ <string>q.glif</string>
+ <key>r</key>
+ <string>r.glif</string>
+ <key>s</key>
+ <string>s.glif</string>
+ <key>space</key>
+ <string>space.glif</string>
+ <key>t</key>
+ <string>t.glif</string>
+ <key>u</key>
+ <string>u.glif</string>
+ <key>v</key>
+ <string>v.glif</string>
+ <key>w</key>
+ <string>w.glif</string>
+ <key>x</key>
+ <string>x.glif</string>
+ <key>y</key>
+ <string>y.glif</string>
+ <key>z</key>
+ <string>z.glif</string>
+ </dict>
+</plist>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/d.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/d.glif
new file mode 100644
index 00000000..c585788c
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/d.glif
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="d" format="2">
+ <unicode hex="0064"/>
+ <advance width="1159"/>
+ <outline>
+ <contour>
+ <point x="815" y="210" type="line"/>
+ <point x="832" y="0" type="line"/>
+ <point x="1015" y="0" type="line"/>
+ <point x="1015" y="1536" type="line"/>
+ <point x="815" y="1536" type="line"/>
+ </contour>
+ <contour>
+ <point x="92" y="529" type="line"/>
+ <point x="92" y="214"/>
+ <point x="266" y="-20"/>
+ <point x="520" y="-20" type="curve" smooth="yes"/>
+ <point x="798" y="-20"/>
+ <point x="931" y="181"/>
+ <point x="960" y="510" type="curve"/>
+ <point x="960" y="572" type="line"/>
+ <point x="931" y="901"/>
+ <point x="798" y="1102"/>
+ <point x="522" y="1102" type="curve" smooth="yes"/>
+ <point x="264" y="1102"/>
+ <point x="92" y="879"/>
+ <point x="92" y="550" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="292" y="550" type="line"/>
+ <point x="292" y="759"/>
+ <point x="375" y="937"/>
+ <point x="573" y="937" type="curve" smooth="yes"/>
+ <point x="750" y="937"/>
+ <point x="834" y="803"/>
+ <point x="865" y="655" type="curve"/>
+ <point x="865" y="426" type="line"/>
+ <point x="824" y="277"/>
+ <point x="750" y="146"/>
+ <point x="571" y="146" type="curve" smooth="yes"/>
+ <point x="375" y="146"/>
+ <point x="292" y="320"/>
+ <point x="292" y="529" type="curve" smooth="yes"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/e.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/e.glif
new file mode 100644
index 00000000..29d3ecdc
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/e.glif
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="e" format="2">
+ <unicode hex="0065"/>
+ <advance width="1092"/>
+ <outline>
+ <contour>
+ <point x="593" y="-20" type="curve"/>
+ <point x="812" y="-20"/>
+ <point x="936" y="85"/>
+ <point x="1006" y="192" type="curve"/>
+ <point x="885" y="286" type="line"/>
+ <point x="820" y="200"/>
+ <point x="735" y="140"/>
+ <point x="604" y="140" type="curve" smooth="yes"/>
+ <point x="406" y="140"/>
+ <point x="294" y="302"/>
+ <point x="294" y="502" type="curve" smooth="yes"/>
+ <point x="294" y="544" type="line" smooth="yes"/>
+ <point x="294" y="803"/>
+ <point x="407" y="942"/>
+ <point x="569" y="942" type="curve"/>
+ <point x="755" y="942"/>
+ <point x="808" y="792"/>
+ <point x="818" y="655" type="curve"/>
+ <point x="818" y="641" type="line"/>
+ <point x="212" y="641" type="line"/>
+ <point x="212" y="481" type="line"/>
+ <point x="1018" y="481" type="line"/>
+ <point x="1018" y="566" type="line" smooth="yes"/>
+ <point x="1018" y="871"/>
+ <point x="887" y="1102"/>
+ <point x="569" y="1102" type="curve" smooth="yes"/>
+ <point x="326" y="1102"/>
+ <point x="94" y="900"/>
+ <point x="94" y="544" type="curve" smooth="yes"/>
+ <point x="94" y="502" type="line" smooth="yes"/>
+ <point x="94" y="198"/>
+ <point x="287" y="-20"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/f.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/f.glif
new file mode 100644
index 00000000..56e63f72
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/f.glif
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="f" format="2">
+ <unicode hex="0066"/>
+ <advance width="719"/>
+ <outline>
+ <contour>
+ <point x="429" y="0" type="line"/>
+ <point x="429" y="1193" type="line"/>
+ <point x="429" y="1320"/>
+ <point x="496" y="1390"/>
+ <point x="612" y="1390" type="curve" smooth="yes"/>
+ <point x="645" y="1390"/>
+ <point x="683" y="1387"/>
+ <point x="710" y="1382" type="curve"/>
+ <point x="720" y="1541" type="line"/>
+ <point x="679" y="1551"/>
+ <point x="635" y="1557"/>
+ <point x="593" y="1557" type="curve" smooth="yes"/>
+ <point x="367" y="1557"/>
+ <point x="229" y="1428"/>
+ <point x="229" y="1193" type="curve"/>
+ <point x="229" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="653" y="1082" type="line"/>
+ <point x="60" y="1082" type="line"/>
+ <point x="60" y="932" type="line"/>
+ <point x="653" y="932" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/g.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/g.glif
new file mode 100644
index 00000000..130a3ed4
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/g.glif
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="g" format="2">
+ <unicode hex="0067"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="836" y="1082" type="line"/>
+ <point x="817" y="844" type="line"/>
+ <point x="817" y="14" type="line" smooth="yes"/>
+ <point x="817" y="-170"/>
+ <point x="706" y="-266"/>
+ <point x="538" y="-266" type="curve" smooth="yes"/>
+ <point x="444" y="-266"/>
+ <point x="341" y="-232"/>
+ <point x="250" y="-121" type="curve"/>
+ <point x="147" y="-238" type="line"/>
+ <point x="246" y="-382"/>
+ <point x="446" y="-426"/>
+ <point x="553" y="-426" type="curve" smooth="yes"/>
+ <point x="825" y="-426"/>
+ <point x="1017" y="-264"/>
+ <point x="1017" y="24" type="curve" smooth="yes"/>
+ <point x="1017" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="94" y="529" type="line"/>
+ <point x="94" y="214"/>
+ <point x="261" y="-20"/>
+ <point x="521" y="-20" type="curve" smooth="yes"/>
+ <point x="799" y="-20"/>
+ <point x="932" y="181"/>
+ <point x="962" y="510" type="curve"/>
+ <point x="962" y="572" type="line"/>
+ <point x="932" y="901"/>
+ <point x="799" y="1102"/>
+ <point x="523" y="1102" type="curve" smooth="yes"/>
+ <point x="259" y="1102"/>
+ <point x="94" y="879"/>
+ <point x="94" y="550" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="294" y="550" type="line"/>
+ <point x="294" y="759"/>
+ <point x="376" y="937"/>
+ <point x="574" y="937" type="curve" smooth="yes"/>
+ <point x="751" y="937"/>
+ <point x="835" y="803"/>
+ <point x="866" y="655" type="curve"/>
+ <point x="866" y="426" type="line"/>
+ <point x="825" y="277"/>
+ <point x="751" y="146"/>
+ <point x="572" y="146" type="curve" smooth="yes"/>
+ <point x="376" y="146"/>
+ <point x="294" y="320"/>
+ <point x="294" y="529" type="curve" smooth="yes"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/h.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/h.glif
new file mode 100644
index 00000000..85defe3f
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/h.glif
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="h" format="2">
+ <unicode hex="0068"/>
+ <advance width="1129"/>
+ <outline>
+ <contour>
+ <point x="337" y="1536" type="line"/>
+ <point x="137" y="1536" type="line"/>
+ <point x="137" y="0" type="line"/>
+ <point x="337" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="289" y="578" type="line"/>
+ <point x="289" y="778"/>
+ <point x="414" y="937"/>
+ <point x="588" y="937" type="curve" smooth="yes"/>
+ <point x="725" y="937"/>
+ <point x="796" y="872"/>
+ <point x="796" y="712" type="curve" smooth="yes"/>
+ <point x="796" y="0" type="line"/>
+ <point x="996" y="0" type="line"/>
+ <point x="996" y="710" type="line" smooth="yes"/>
+ <point x="996" y="993"/>
+ <point x="861" y="1102"/>
+ <point x="649" y="1102" type="curve" smooth="yes"/>
+ <point x="384" y="1102"/>
+ <point x="207" y="880"/>
+ <point x="207" y="576" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/i.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/i.glif
new file mode 100644
index 00000000..2535d418
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/i.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="i" format="2">
+ <unicode hex="0069"/>
+ <advance width="506"/>
+ <outline>
+ <contour>
+ <point x="353" y="1082" type="line"/>
+ <point x="153" y="1082" type="line"/>
+ <point x="153" y="0" type="line"/>
+ <point x="353" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="140" y="1365" type="curve" smooth="yes"/>
+ <point x="140" y="1304"/>
+ <point x="179" y="1256"/>
+ <point x="255" y="1256" type="curve"/>
+ <point x="331" y="1256"/>
+ <point x="371" y="1304"/>
+ <point x="371" y="1365" type="curve"/>
+ <point x="371" y="1427"/>
+ <point x="331" y="1476"/>
+ <point x="255" y="1476" type="curve"/>
+ <point x="179" y="1476"/>
+ <point x="140" y="1427"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/j.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/j.glif
new file mode 100644
index 00000000..64961d35
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/j.glif
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="j" format="2">
+ <unicode hex="006A"/>
+ <advance width="495"/>
+ <outline>
+ <contour>
+ <point x="142" y="1082" type="line"/>
+ <point x="142" y="-129" type="line"/>
+ <point x="142" y="-235"/>
+ <point x="101" y="-271"/>
+ <point x="27" y="-271" type="curve" smooth="yes"/>
+ <point x="5" y="-271"/>
+ <point x="-31" y="-269"/>
+ <point x="-57" y="-263" type="curve"/>
+ <point x="-57" y="-420" type="line"/>
+ <point x="-26" y="-430"/>
+ <point x="25" y="-437"/>
+ <point x="59" y="-437" type="curve" smooth="yes"/>
+ <point x="250" y="-437"/>
+ <point x="342" y="-328"/>
+ <point x="342" y="-129" type="curve"/>
+ <point x="342" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="123" y="1365" type="curve" smooth="yes"/>
+ <point x="123" y="1304"/>
+ <point x="162" y="1256"/>
+ <point x="238" y="1256" type="curve" smooth="yes"/>
+ <point x="314" y="1256"/>
+ <point x="354" y="1304"/>
+ <point x="354" y="1365" type="curve" smooth="yes"/>
+ <point x="354" y="1427"/>
+ <point x="314" y="1476"/>
+ <point x="238" y="1476" type="curve" smooth="yes"/>
+ <point x="162" y="1476"/>
+ <point x="123" y="1427"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/k.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/k.glif
new file mode 100644
index 00000000..708bdec2
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/k.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="k" format="2">
+ <unicode hex="006B"/>
+ <advance width="1046"/>
+ <outline>
+ <contour>
+ <point x="338" y="1536" type="line"/>
+ <point x="138" y="1536" type="line"/>
+ <point x="138" y="0" type="line"/>
+ <point x="338" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="995" y="1082" type="line"/>
+ <point x="753" y="1082" type="line"/>
+ <point x="434" y="735" type="line"/>
+ <point x="241" y="502" type="line"/>
+ <point x="256" y="291" type="line"/>
+ <point x="531" y="577" type="line"/>
+ </contour>
+ <contour>
+ <point x="812" y="0" type="line"/>
+ <point x="1046" y="0" type="line"/>
+ <point x="543" y="684" type="line"/>
+ <point x="440" y="508" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/l.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/l.glif
new file mode 100644
index 00000000..26d4ac3a
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/l.glif
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="l" format="2">
+ <unicode hex="006C"/>
+ <advance width="506"/>
+ <outline>
+ <contour>
+ <point x="353" y="1536" type="line"/>
+ <point x="153" y="1536" type="line"/>
+ <point x="153" y="0" type="line"/>
+ <point x="353" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/m.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/m.glif
new file mode 100644
index 00000000..22f0363f
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/m.glif
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="m" format="2">
+ <unicode hex="006D"/>
+ <advance width="1791"/>
+ <outline>
+ <contour>
+ <point x="337" y="869" type="line"/>
+ <point x="326" y="1082" type="line"/>
+ <point x="137" y="1082" type="line"/>
+ <point x="137" y="0" type="line"/>
+ <point x="337" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="296" y="578" type="line"/>
+ <point x="296" y="778"/>
+ <point x="360" y="937"/>
+ <point x="571" y="937" type="curve" smooth="yes"/>
+ <point x="708" y="937"/>
+ <point x="795" y="872"/>
+ <point x="795" y="711" type="curve"/>
+ <point x="795" y="0" type="line"/>
+ <point x="995" y="0" type="line"/>
+ <point x="995" y="721" type="line"/>
+ <point x="995" y="992"/>
+ <point x="845" y="1102"/>
+ <point x="645" y="1102" type="curve" smooth="yes"/>
+ <point x="350" y="1102"/>
+ <point x="203" y="880"/>
+ <point x="203" y="576" type="curve"/>
+ </contour>
+ <contour>
+ <point x="993" y="681" type="line"/>
+ <point x="993" y="824"/>
+ <point x="1077" y="937"/>
+ <point x="1230" y="937" type="curve" smooth="yes"/>
+ <point x="1367" y="937"/>
+ <point x="1454" y="887"/>
+ <point x="1454" y="714" type="curve" smooth="yes"/>
+ <point x="1454" y="0" type="line"/>
+ <point x="1654" y="0" type="line"/>
+ <point x="1654" y="712" type="line" smooth="yes"/>
+ <point x="1654" y="984"/>
+ <point x="1523" y="1102"/>
+ <point x="1290" y="1102" type="curve" smooth="yes"/>
+ <point x="1009" y="1102"/>
+ <point x="859" y="878"/>
+ <point x="859" y="637" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/n.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/n.glif
new file mode 100644
index 00000000..c85d49b6
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/n.glif
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="n" format="2">
+ <unicode hex="006E"/>
+ <advance width="1132"/>
+ <outline>
+ <contour>
+ <point x="337" y="851" type="line"/>
+ <point x="326" y="1082" type="line"/>
+ <point x="137" y="1082" type="line"/>
+ <point x="137" y="0" type="line"/>
+ <point x="337" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="289" y="578" type="line"/>
+ <point x="289" y="778"/>
+ <point x="414" y="937"/>
+ <point x="588" y="937" type="curve" smooth="yes"/>
+ <point x="725" y="937"/>
+ <point x="796" y="872"/>
+ <point x="796" y="712" type="curve" smooth="yes"/>
+ <point x="796" y="0" type="line"/>
+ <point x="996" y="0" type="line"/>
+ <point x="996" y="710" type="line" smooth="yes"/>
+ <point x="996" y="993"/>
+ <point x="861" y="1102"/>
+ <point x="649" y="1102" type="curve" smooth="yes"/>
+ <point x="384" y="1102"/>
+ <point x="207" y="880"/>
+ <point x="207" y="576" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/o.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/o.glif
new file mode 100644
index 00000000..a95b37aa
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/o.glif
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="o" format="2">
+ <unicode hex="006F"/>
+ <advance width="1173"/>
+ <outline>
+ <contour>
+ <point x="92" y="530" type="line"/>
+ <point x="92" y="217"/>
+ <point x="281" y="-20"/>
+ <point x="587" y="-20" type="curve" smooth="yes"/>
+ <point x="893" y="-20"/>
+ <point x="1081" y="217"/>
+ <point x="1081" y="530" type="curve"/>
+ <point x="1081" y="551" type="line"/>
+ <point x="1081" y="864"/>
+ <point x="893" y="1102"/>
+ <point x="585" y="1102" type="curve" smooth="yes"/>
+ <point x="281" y="1102"/>
+ <point x="92" y="864"/>
+ <point x="92" y="551" type="curve"/>
+ </contour>
+ <contour>
+ <point x="292" y="551" type="line"/>
+ <point x="292" y="760"/>
+ <point x="388" y="942"/>
+ <point x="585" y="942" type="curve" smooth="yes"/>
+ <point x="784" y="942"/>
+ <point x="881" y="760"/>
+ <point x="881" y="551" type="curve"/>
+ <point x="881" y="530" type="line"/>
+ <point x="881" y="319"/>
+ <point x="784" y="140"/>
+ <point x="587" y="140" type="curve" smooth="yes"/>
+ <point x="388" y="140"/>
+ <point x="292" y="319"/>
+ <point x="292" y="530" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/p.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/p.glif
new file mode 100644
index 00000000..c003eba2
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/p.glif
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="p" format="2">
+ <unicode hex="0070"/>
+ <advance width="1153"/>
+ <outline>
+ <contour>
+ <point x="337" y="874" type="line"/>
+ <point x="319" y="1082" type="line"/>
+ <point x="137" y="1082" type="line"/>
+ <point x="137" y="-416" type="line"/>
+ <point x="337" y="-416" type="line"/>
+ </contour>
+ <contour>
+ <point x="1061" y="550" type="line"/>
+ <point x="1061" y="879"/>
+ <point x="911" y="1102"/>
+ <point x="637" y="1102" type="curve" smooth="yes"/>
+ <point x="360" y="1102"/>
+ <point x="219" y="901"/>
+ <point x="189" y="572" type="curve"/>
+ <point x="189" y="489" type="line"/>
+ <point x="219" y="173"/>
+ <point x="360" y="-20"/>
+ <point x="640" y="-20" type="curve" smooth="yes"/>
+ <point x="909" y="-20"/>
+ <point x="1061" y="214"/>
+ <point x="1061" y="529" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="861" y="529" type="line"/>
+ <point x="861" y="320"/>
+ <point x="774" y="140"/>
+ <point x="578" y="140" type="curve" smooth="yes"/>
+ <point x="400" y="140"/>
+ <point x="325" y="267"/>
+ <point x="285" y="406" type="curve"/>
+ <point x="285" y="655" type="line"/>
+ <point x="315" y="803"/>
+ <point x="400" y="937"/>
+ <point x="576" y="937" type="curve" smooth="yes"/>
+ <point x="774" y="937"/>
+ <point x="861" y="759"/>
+ <point x="861" y="550" type="curve" smooth="yes"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/q.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/q.glif
new file mode 100644
index 00000000..04d7b001
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/q.glif
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="q" format="2">
+ <unicode hex="0071"/>
+ <advance width="1169"/>
+ <outline>
+ <contour>
+ <point x="814" y="-416" type="line"/>
+ <point x="1014" y="-416" type="line"/>
+ <point x="1014" y="1082" type="line"/>
+ <point x="831" y="1082" type="line"/>
+ <point x="814" y="874" type="line"/>
+ </contour>
+ <contour>
+ <point x="92" y="529" type="line"/>
+ <point x="92" y="214"/>
+ <point x="255" y="-20"/>
+ <point x="524" y="-20" type="curve" smooth="yes"/>
+ <point x="802" y="-20"/>
+ <point x="941" y="181"/>
+ <point x="970" y="510" type="curve"/>
+ <point x="970" y="572" type="line"/>
+ <point x="941" y="901"/>
+ <point x="802" y="1102"/>
+ <point x="526" y="1102" type="curve" smooth="yes"/>
+ <point x="253" y="1102"/>
+ <point x="92" y="879"/>
+ <point x="92" y="550" type="curve" smooth="yes"/>
+ </contour>
+ <contour>
+ <point x="292" y="550" type="line"/>
+ <point x="292" y="759"/>
+ <point x="379" y="942"/>
+ <point x="577" y="942" type="curve" smooth="yes"/>
+ <point x="754" y="942"/>
+ <point x="844" y="803"/>
+ <point x="875" y="655" type="curve"/>
+ <point x="875" y="426" type="line"/>
+ <point x="834" y="277"/>
+ <point x="754" y="140"/>
+ <point x="575" y="140" type="curve" smooth="yes"/>
+ <point x="379" y="140"/>
+ <point x="292" y="320"/>
+ <point x="292" y="529" type="curve" smooth="yes"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/r.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/r.glif
new file mode 100644
index 00000000..5e222cc1
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/r.glif
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="r" format="2">
+ <unicode hex="0072"/>
+ <advance width="695"/>
+ <outline>
+ <contour>
+ <point x="337" y="914" type="line"/>
+ <point x="331" y="1082" type="line"/>
+ <point x="137" y="1082" type="line"/>
+ <point x="137" y="0" type="line"/>
+ <point x="337" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="665" y="1088" type="line"/>
+ <point x="652" y="1094"/>
+ <point x="607" y="1102"/>
+ <point x="582" y="1102" type="curve"/>
+ <point x="352" y="1102"/>
+ <point x="253" y="884"/>
+ <point x="253" y="628" type="curve"/>
+ <point x="307" y="660" type="line"/>
+ <point x="325" y="809"/>
+ <point x="408" y="913"/>
+ <point x="572" y="913" type="curve"/>
+ <point x="608" y="913"/>
+ <point x="632" y="911"/>
+ <point x="665" y="905" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/s.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/s.glif
new file mode 100644
index 00000000..95444036
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/s.glif
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="s" format="2">
+ <unicode hex="0073"/>
+ <advance width="1061"/>
+ <outline>
+ <contour>
+ <point x="763" y="289" type="curve" smooth="yes"/>
+ <point x="763" y="202"/>
+ <point x="685" y="140"/>
+ <point x="541" y="140" type="curve" smooth="yes"/>
+ <point x="430" y="140"/>
+ <point x="302" y="189"/>
+ <point x="294" y="339" type="curve"/>
+ <point x="94" y="339" type="line"/>
+ <point x="94" y="158"/>
+ <point x="251" y="-20"/>
+ <point x="541" y="-20" type="curve" smooth="yes"/>
+ <point x="795" y="-20"/>
+ <point x="963" y="112"/>
+ <point x="963" y="304" type="curve"/>
+ <point x="963" y="479"/>
+ <point x="838" y="567"/>
+ <point x="572" y="628" type="curve" smooth="yes"/>
+ <point x="376" y="672"/>
+ <point x="331" y="710"/>
+ <point x="331" y="788" type="curve"/>
+ <point x="331" y="865"/>
+ <point x="389" y="942"/>
+ <point x="536" y="942" type="curve" smooth="yes"/>
+ <point x="674" y="942"/>
+ <point x="751" y="847"/>
+ <point x="751" y="762" type="curve"/>
+ <point x="951" y="762" type="line"/>
+ <point x="951" y="948"/>
+ <point x="797" y="1102"/>
+ <point x="536" y="1102" type="curve" smooth="yes"/>
+ <point x="291" y="1102"/>
+ <point x="131" y="955"/>
+ <point x="131" y="782" type="curve"/>
+ <point x="131" y="604"/>
+ <point x="283" y="519"/>
+ <point x="523" y="469" type="curve" smooth="yes"/>
+ <point x="731" y="427"/>
+ <point x="763" y="367"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/space.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/space.glif
new file mode 100644
index 00000000..b92e22c9
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/space.glif
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="space" format="2">
+ <unicode hex="0020"/>
+ <advance width="510"/>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/t.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/t.glif
new file mode 100644
index 00000000..261e70a7
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/t.glif
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="t" format="2">
+ <unicode hex="0074"/>
+ <advance width="672"/>
+ <outline>
+ <contour>
+ <point x="600" y="1082" type="line"/>
+ <point x="6" y="1082" type="line"/>
+ <point x="6" y="932" type="line"/>
+ <point x="600" y="932" type="line"/>
+ </contour>
+ <contour>
+ <point x="203" y="1342" type="line"/>
+ <point x="203" y="270" type="line"/>
+ <point x="203" y="53"/>
+ <point x="319" y="-20"/>
+ <point x="456" y="-20" type="curve" smooth="yes"/>
+ <point x="526" y="-20"/>
+ <point x="573" y="-8"/>
+ <point x="602" y="1" type="curve"/>
+ <point x="602" y="160" type="line"/>
+ <point x="587" y="156"/>
+ <point x="547" y="148"/>
+ <point x="517" y="148" type="curve" smooth="yes"/>
+ <point x="458" y="148"/>
+ <point x="403" y="165"/>
+ <point x="403" y="269" type="curve"/>
+ <point x="403" y="1342" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/u.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/u.glif
new file mode 100644
index 00000000..c17bfd9e
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/u.glif
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="u" format="2">
+ <unicode hex="0075"/>
+ <advance width="1130"/>
+ <outline>
+ <contour>
+ <point x="793" y="250" type="line"/>
+ <point x="803" y="0" type="line"/>
+ <point x="993" y="0" type="line"/>
+ <point x="993" y="1082" type="line"/>
+ <point x="793" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="830" y="483" type="line"/>
+ <point x="830" y="293"/>
+ <point x="750" y="146"/>
+ <point x="521" y="146" type="curve" smooth="yes"/>
+ <point x="428" y="146"/>
+ <point x="333" y="192"/>
+ <point x="333" y="381" type="curve"/>
+ <point x="333" y="1082" type="line"/>
+ <point x="133" y="1082" type="line"/>
+ <point x="133" y="383" type="line"/>
+ <point x="133" y="96"/>
+ <point x="276" y="-20"/>
+ <point x="488" y="-20" type="curve" smooth="yes"/>
+ <point x="800" y="-20"/>
+ <point x="911" y="194"/>
+ <point x="911" y="485" type="curve"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/v.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/v.glif
new file mode 100644
index 00000000..bd5b120f
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/v.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="v" format="2">
+ <unicode hex="0076"/>
+ <advance width="994"/>
+ <outline>
+ <contour>
+ <point x="469" y="176" type="line"/>
+ <point x="439" y="0" type="line"/>
+ <point x="572" y="0" type="line"/>
+ <point x="957" y="1082" type="line"/>
+ <point x="753" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="236" y="1082" type="line"/>
+ <point x="32" y="1082" type="line"/>
+ <point x="421" y="0" type="line"/>
+ <point x="553" y="0" type="line"/>
+ <point x="531" y="171" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/w.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/w.glif
new file mode 100644
index 00000000..e6b648e1
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/w.glif
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="w" format="2">
+ <unicode hex="0077"/>
+ <advance width="1538"/>
+ <outline>
+ <contour>
+ <point x="406" y="182" type="line"/>
+ <point x="388" y="0" type="line"/>
+ <point x="514" y="0" type="line"/>
+ <point x="799" y="913" type="line"/>
+ <point x="818" y="1082" type="line"/>
+ <point x="687" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="237" y="1082" type="line"/>
+ <point x="39" y="1082" type="line"/>
+ <point x="353" y="0" type="line"/>
+ <point x="486" y="0" type="line"/>
+ <point x="476" y="171" type="line"/>
+ </contour>
+ <contour>
+ <point x="1071" y="178" type="line"/>
+ <point x="1049" y="0" type="line"/>
+ <point x="1181" y="0" type="line"/>
+ <point x="1495" y="1082" type="line"/>
+ <point x="1297" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="847" y="1082" type="line"/>
+ <point x="708" y="1082" type="line"/>
+ <point x="727" y="915" type="line"/>
+ <point x="1020" y="0" type="line"/>
+ <point x="1146" y="0" type="line"/>
+ <point x="1122" y="198" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/x.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/x.glif
new file mode 100644
index 00000000..8a603138
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/x.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="x" format="2">
+ <unicode hex="0078"/>
+ <advance width="1020"/>
+ <outline>
+ <contour>
+ <point x="280" y="1082" type="line"/>
+ <point x="50" y="1082" type="line"/>
+ <point x="401" y="547" type="line"/>
+ <point x="40" y="0" type="line"/>
+ <point x="272" y="0" type="line"/>
+ <point x="509" y="397" type="line"/>
+ <point x="746" y="0" type="line"/>
+ <point x="976" y="0" type="line"/>
+ <point x="615" y="547" type="line"/>
+ <point x="966" y="1082" type="line"/>
+ <point x="733" y="1082" type="line"/>
+ <point x="505" y="695" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/y.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/y.glif
new file mode 100644
index 00000000..793d4f28
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/y.glif
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="y" format="2">
+ <unicode hex="0079"/>
+ <advance width="968"/>
+ <outline>
+ <contour>
+ <point x="438" y="113" type="line"/>
+ <point x="363" y="-92" type="line" smooth="yes"/>
+ <point x="319" y="-226"/>
+ <point x="255" y="-266"/>
+ <point x="130" y="-266" type="curve" smooth="yes"/>
+ <point x="121" y="-266"/>
+ <point x="93" y="-264"/>
+ <point x="83" y="-262" type="curve"/>
+ <point x="83" y="-421" type="line"/>
+ <point x="103" y="-426"/>
+ <point x="159" y="-437"/>
+ <point x="190" y="-437" type="curve" smooth="yes"/>
+ <point x="386" y="-437"/>
+ <point x="475" y="-274"/>
+ <point x="515" y="-166" type="curve" smooth="yes"/>
+ <point x="944" y="1082" type="line"/>
+ <point x="731" y="1082" type="line"/>
+ </contour>
+ <contour>
+ <point x="238" y="1082" type="line"/>
+ <point x="20" y="1082" type="line"/>
+ <point x="413" y="-21" type="line"/>
+ <point x="556" y="50" type="line"/>
+ <point x="504" y="258" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/z.glif b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/z.glif
new file mode 100644
index 00000000..e21d52af
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/glyphs/z.glif
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="z" format="2">
+ <unicode hex="007A"/>
+ <advance width="1020"/>
+ <outline>
+ <contour>
+ <point x="949" y="160" type="line"/>
+ <point x="166" y="160" type="line"/>
+ <point x="166" y="0" type="line"/>
+ <point x="949" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="923" y="944" type="line"/>
+ <point x="923" y="1082" type="line"/>
+ <point x="796" y="1082" type="line"/>
+ <point x="89" y="144" type="line"/>
+ <point x="89" y="0" type="line"/>
+ <point x="211" y="0" type="line"/>
+ </contour>
+ <contour>
+ <point x="835" y="1082" type="line"/>
+ <point x="95" y="1082" type="line"/>
+ <point x="95" y="921" type="line"/>
+ <point x="835" y="921" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/layercontents.plist b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/layercontents.plist
new file mode 100644
index 00000000..03e5dde5
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/layercontents.plist
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <array>
+ <array>
+ <string>public.default</string>
+ <string>glyphs</string>
+ </array>
+ </array>
+</plist>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/lib.plist b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/lib.plist
new file mode 100644
index 00000000..5623ed1b
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/lib.plist
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>public.glyphOrder</key>
+ <array>
+ <string>space</string>
+ <string>A</string>
+ <string>B</string>
+ <string>C</string>
+ <string>D</string>
+ <string>E</string>
+ <string>F</string>
+ <string>G</string>
+ <string>H</string>
+ <string>I</string>
+ <string>J</string>
+ <string>K</string>
+ <string>L</string>
+ <string>M</string>
+ <string>N</string>
+ <string>O</string>
+ <string>P</string>
+ <string>Q</string>
+ <string>R</string>
+ <string>S</string>
+ <string>T</string>
+ <string>U</string>
+ <string>V</string>
+ <string>W</string>
+ <string>X</string>
+ <string>Y</string>
+ <string>Z</string>
+ <string>a</string>
+ <string>b</string>
+ <string>c</string>
+ <string>d</string>
+ <string>e</string>
+ <string>f</string>
+ <string>g</string>
+ <string>h</string>
+ <string>i</string>
+ <string>j</string>
+ <string>k</string>
+ <string>l</string>
+ <string>m</string>
+ <string>n</string>
+ <string>o</string>
+ <string>p</string>
+ <string>q</string>
+ <string>r</string>
+ <string>s</string>
+ <string>t</string>
+ <string>u</string>
+ <string>v</string>
+ <string>w</string>
+ <string>x</string>
+ <string>y</string>
+ <string>z</string>
+ </array>
+ </dict>
+</plist>
diff --git a/Tests/cu2qu/data/RobotoSubset-Regular.ufo/metainfo.plist b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/metainfo.plist
new file mode 100644
index 00000000..edfd6376
--- /dev/null
+++ b/Tests/cu2qu/data/RobotoSubset-Regular.ufo/metainfo.plist
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>creator</key>
+ <string>org.robofab.ufoLib</string>
+ <key>formatVersion</key>
+ <integer>3</integer>
+ </dict>
+</plist>
diff --git a/Tests/cu2qu/data/curves.json b/Tests/cu2qu/data/curves.json
new file mode 100644
index 00000000..23af5752
--- /dev/null
+++ b/Tests/cu2qu/data/curves.json
@@ -0,0 +1 @@
+[[[550.0,258.0],[1044.0,482.0],[2029.0,1841.0],[1934.0,1554.0]],[[859.0,384.0],[1998.0,116.0],[1596.0,1772.0],[8.0,1824.0]],[[1090.0,937.0],[418.0,1300.0],[125.0,91.0],[104.0,37.0]],[[1561.0,887.0],[1728.0,118.0],[908.0,1793.0],[2030.0,954.0]],[[1415.0,945.0],[896.0,1882.0],[1186.0,88.0],[1704.0,409.0]],[[761.0,1214.0],[495.0,1362.0],[1728.0,777.0],[1242.0,1163.0]],[[2045.0,1611.0],[141.0,1967.0],[994.0,1655.0],[1697.0,708.0]],[[1503.0,1534.0],[354.0,1797.0],[442.0,670.0],[1610.0,1517.0]],[[2005.0,121.0],[1922.0,178.0],[1263.0,1612.0],[697.0,690.0]],[[929.0,50.0],[817.0,950.0],[1656.0,1408.0],[1447.0,1880.0]],[[1102.0,23.0],[1571.0,529.0],[841.0,1745.0],[229.0,1970.0]],[[1493.0,818.0],[1693.0,1986.0],[1461.0,1697.0],[1417.0,6.0]],[[1356.0,1876.0],[114.0,940.0],[725.0,740.0],[375.0,1045.0]],[[132.0,288.0],[340.0,68.0],[1855.0,59.0],[1151.0,1022.0]],[[1100.0,448.0],[756.0,1410.0],[1189.0,284.0],[685.0,653.0]],[[1045.0,688.0],[1117.0,1206.0],[1862.0,1318.0],[2033.0,1940.0]],[[467.0,96.0],[1277.0,1583.0],[1406.0,1724.0],[770.0,1058.0]],[[445.0,1038.0],[856.0,1768.0],[85.0,923.0],[73.0,1627.0]],[[599.0,144.0],[656.0,1825.0],[1747.0,903.0],[1846.0,914.0]],[[125.0,1617.0],[1315.0,1746.0],[240.0,1223.0],[514.0,868.0]],[[194.0,1254.0],[289.0,313.0],[1271.0,1220.0],[648.0,1704.0]],[[1033.0,534.0],[34.0,155.0],[891.0,1887.0],[702.0,153.0]],[[1548.0,820.0],[1421.0,405.0],[842.0,1773.0],[795.0,2016.0]],[[427.0,1597.0],[1212.0,2047.0],[70.0,1332.0],[1647.0,1152.0]],[[74.0,642.0],[822.0,1342.0],[553.0,1388.0],[1758.0,872.0]],[[1091.0,394.0],[1553.0,1408.0],[1984.0,961.0],[267.0,165.0]],[[346.0,544.0],[695.0,682.0],[872.0,1097.0],[1360.0,1045.0]],[[1507.0,1387.0],[1393.0,466.0],[1192.0,963.0],[2002.0,554.0]],[[427.0,1313.0],[160.0,1665.0],[299.0,1557.0],[603.0,512.0]],[[1396.0,469.0],[1548.0,313.0],[916.0,334.0],[1092.0,1494.0]],[[1210.0,468.0],[1875.0,1135.0],[441.0,187.0],[1211.0,50.0]],[[59.0,375.0],[1693.0,471.0],[163.0,769.0],[981.0,1724.0]],[[663.0,473.0],[1846.0,685.0],[988.0,651.0],[421.0,1782.0]],[[1549.0,1204.0],[1037.0,1953.0],[1288.0,410.0],[850.0,1300.0]],[[162.0,111.0],[43.0,1210.0],[1311.0,1842.0],[1602.0,1283.0]],[[1632.0,257.0],[262.0,1299.0],[1867.0,456.0],[1024.0,881.0]],[[1920.0,1457.0],[1061.0,750.0],[851.0,1258.0],[815.0,1009.0]],[[1476.0,333.0],[1150.0,366.0],[1834.0,370.0],[1388.0,931.0]],[[1599.0,1256.0],[168.0,1340.0],[765.0,1297.0],[1240.0,1006.0]],[[1369.0,413.0],[377.0,1003.0],[901.0,83.0],[998.0,1645.0]],[[296.0,1097.0],[290.0,307.0],[88.0,40.0],[1191.0,1471.0]],[[2020.0,1920.0],[631.0,413.0],[1343.0,315.0],[709.0,735.0]],[[612.0,579.0],[1309.0,1251.0],[437.0,1202.0],[517.0,846.0]],[[580.0,130.0],[1294.0,841.0],[729.0,1224.0],[1772.0,646.0]],[[198.0,1012.0],[1034.0,263.0],[1829.0,1761.0],[1024.0,1799.0]],[[1856.0,44.0],[1620.0,1387.0],[702.0,1056.0],[1989.0,99.0]],[[1706.0,77.0],[255.0,1453.0],[566.0,512.0],[567.0,1061.0]],[[1134.0,1629.0],[1642.0,705.0],[365.0,956.0],[1990.0,30.0]],[[727.0,1299.0],[1795.0,924.0],[976.0,1281.0],[2027.0,1961.0]],[[921.0,1688.0],[1380.0,1127.0],[898.0,197.0],[293.0,1510.0]],[[653.0,834.0],[1277.0,1223.0],[1227.0,1522.0],[676.0,1903.0]],[[348.0,504.0],[1545.0,722.0],[638.0,1026.0],[1747.0,891.0]],[[213.0,2027.0],[1612.0,1425.0],[1572.0,675.0],[166.0,370.0]],[[1045.0,413.0],[1095.0,342.0],[569.0,335.0],[1822.0,987.0]],[[1566.0,1773.0],[1627.0,674.0],[1333.0,1794.0],[517.0,1998.0]],[[868.0,488.0],[1766.0,1672.0],[483.0,1210.0],[1137.0,1016.0]],[[1551.0,16.0],[777.0,1797.0],[86.0,126.0],[992.0,1066.0]],[[846.0,708.0],[1166.0,607.0],[821.0,1119.0],[1274.0,1027.0]],[[1828.0,688.0],[1462.0,2010.0],[1720.0,498.0],[855.0,1569.0]],[[838.0,1163.0],[442.0,98.0],[483.0,54.0],[1214.0,559.0]],[[307.0,1530.0],[1274.0,1790.0],[1461.0,1325.0],[3.0,507.0]],[[1811.0,1841.0],[1434.0,1248.0],[1635.0,1390.0],[2016.0,463.0]],[[1546.0,1566.0],[835.0,15.0],[1137.0,814.0],[1890.0,1675.0]],[[1250.0,697.0],[1840.0,808.0],[1472.0,14.0],[1594.0,1744.0]],[[1659.0,1376.0],[277.0,2018.0],[1014.0,1191.0],[85.0,1667.0]],[[639.0,1627.0],[1106.0,729.0],[300.0,41.0],[1431.0,1083.0]],[[1684.0,1243.0],[622.0,1892.0],[1062.0,1984.0],[694.0,1913.0]],[[185.0,1109.0],[403.0,1730.0],[285.0,1454.0],[274.0,1812.0]],[[80.0,672.0],[662.0,381.0],[1646.0,1129.0],[1246.0,855.0]],[[850.0,971.0],[1367.0,1102.0],[280.0,306.0],[1508.0,1916.0]],[[203.0,690.0],[1216.0,1104.0],[1457.0,950.0],[1607.0,1637.0]],[[705.0,1980.0],[1063.0,1350.0],[910.0,1059.0],[1000.0,125.0]],[[1649.0,1296.0],[1768.0,1017.0],[1102.0,777.0],[297.0,678.0]],[[1816.0,606.0],[1073.0,1881.0],[665.0,567.0],[565.0,1805.0]],[[1479.0,1268.0],[1641.0,985.0],[474.0,844.0],[1251.0,279.0]],[[435.0,932.0],[1626.0,1316.0],[2016.0,409.0],[764.0,184.0]],[[226.0,95.0],[887.0,142.0],[2025.0,1811.0],[1402.0,1124.0]],[[483.0,707.0],[390.0,909.0],[1637.0,955.0],[2027.0,1842.0]],[[1547.0,690.0],[949.0,965.0],[1161.0,1894.0],[1595.0,867.0]],[[1850.0,1056.0],[1352.0,2032.0],[454.0,875.0],[322.0,189.0]],[[63.0,21.0],[1967.0,1308.0],[1569.0,1176.0],[802.0,1638.0]],[[655.0,623.0],[124.0,62.0],[1586.0,594.0],[233.0,1554.0]],[[1041.0,532.0],[325.0,1895.0],[1242.0,59.0],[145.0,249.0]],[[528.0,175.0],[1120.0,481.0],[1771.0,372.0],[778.0,113.0]],[[2046.0,533.0],[1143.0,786.0],[1833.0,1596.0],[1350.0,1097.0]],[[1064.0,995.0],[1005.0,246.0],[717.0,1432.0],[1755.0,249.0]],[[1446.0,1690.0],[816.0,1737.0],[287.0,1094.0],[296.0,1030.0]],[[727.0,395.0],[618.0,240.0],[832.0,1753.0],[183.0,216.0]],[[373.0,1921.0],[1516.0,406.0],[1280.0,164.0],[518.0,135.0]],[[1815.0,525.0],[1618.0,1827.0],[100.0,1105.0],[370.0,1024.0]],[[1332.0,351.0],[1236.0,140.0],[1573.0,238.0],[1069.0,1282.0]],[[532.0,1066.0],[1557.0,479.0],[1244.0,385.0],[1740.0,1005.0]],[[841.0,1352.0],[1387.0,1601.0],[1970.0,428.0],[531.0,1837.0]],[[123.0,1193.0],[643.0,819.0],[1516.0,1594.0],[1328.0,398.0]],[[1677.0,1414.0],[517.0,265.0],[178.0,1230.0],[1284.0,1710.0]],[[1221.0,1305.0],[1444.0,1116.0],[1332.0,35.0],[499.0,609.0]],[[1298.0,1333.0],[1341.0,281.0],[1850.0,1145.0],[1964.0,1860.0]],[[1491.0,1558.0],[320.0,229.0],[551.0,199.0],[2015.0,1031.0]],[[1005.0,1387.0],[1481.0,1516.0],[1648.0,1259.0],[1902.0,1394.0]],[[687.0,119.0],[607.0,1024.0],[905.0,546.0],[461.0,756.0]],[[1683.0,205.0],[406.0,1088.0],[438.0,836.0],[1071.0,273.0]],[[321.0,298.0],[890.0,710.0],[1769.0,89.0],[1507.0,1993.0]],[[1162.0,900.0],[820.0,2021.0],[963.0,1742.0],[1852.0,1503.0]],[[773.0,1974.0],[297.0,1050.0],[1668.0,824.0],[33.0,1559.0]],[[1995.0,312.0],[1653.0,1743.0],[164.0,1441.0],[1877.0,26.0]],[[777.0,1226.0],[22.0,491.0],[1239.0,1292.0],[1157.0,1685.0]],[[1672.0,1260.0],[1853.0,1236.0],[536.0,1819.0],[574.0,667.0]],[[1035.0,39.0],[1737.0,148.0],[1508.0,1723.0],[1647.0,1153.0]],[[75.0,370.0],[368.0,19.0],[1570.0,1101.0],[1902.0,1113.0]],[[1526.0,1971.0],[1378.0,1591.0],[1868.0,477.0],[1981.0,1452.0]],[[592.0,1700.0],[607.0,74.0],[704.0,1065.0],[1506.0,520.0]],[[1176.0,1691.0],[1056.0,1176.0],[1723.0,1120.0],[1775.0,1375.0]],[[1989.0,882.0],[2012.0,1646.0],[1741.0,374.0],[263.0,530.0]],[[844.0,612.0],[938.0,107.0],[422.0,1037.0],[637.0,1965.0]],[[405.0,1634.0],[767.0,12.0],[365.0,1751.0],[208.0,894.0]],[[1728.0,1420.0],[192.0,422.0],[1718.0,485.0],[1086.0,1141.0]],[[733.0,1964.0],[195.0,877.0],[357.0,1596.0],[507.0,1832.0]],[[1205.0,2039.0],[1610.0,475.0],[1962.0,433.0],[610.0,1582.0]],[[824.0,684.0],[1055.0,1706.0],[1182.0,2017.0],[879.0,1380.0]],[[1990.0,421.0],[35.0,1420.0],[1095.0,231.0],[1803.0,1228.0]],[[412.0,936.0],[1124.0,1107.0],[1009.0,1686.0],[607.0,533.0]],[[1049.0,799.0],[1670.0,239.0],[609.0,1694.0],[1106.0,1146.0]],[[1966.0,1252.0],[1093.0,2012.0],[878.0,2042.0],[1506.0,1927.0]],[[989.0,1386.0],[721.0,742.0],[1847.0,612.0],[238.0,1335.0]],[[553.0,873.0],[1291.0,2022.0],[1967.0,1351.0],[484.0,523.0]],[[573.0,1050.0],[921.0,360.0],[204.0,704.0],[475.0,926.0]],[[816.0,1261.0],[1729.0,1342.0],[17.0,82.0],[1250.0,902.0]],[[346.0,919.0],[1147.0,1397.0],[1102.0,1553.0],[94.0,498.0]],[[1351.0,1421.0],[571.0,464.0],[1027.0,586.0],[168.0,1421.0]],[[316.0,376.0],[422.0,1228.0],[1298.0,1019.0],[1103.0,203.0]],[[1481.0,127.0],[320.0,569.0],[1635.0,1523.0],[991.0,384.0]],[[1346.0,1120.0],[32.0,1318.0],[459.0,1443.0],[515.0,1110.0]],[[1659.0,373.0],[1947.0,1715.0],[1612.0,1233.0],[898.0,1239.0]],[[545.0,220.0],[450.0,717.0],[985.0,880.0],[1780.0,1124.0]],[[81.0,1025.0],[1109.0,1072.0],[1938.0,516.0],[1651.0,424.0]],[[1529.0,282.0],[1487.0,124.0],[1262.0,1824.0],[541.0,638.0]],[[304.0,581.0],[885.0,1982.0],[1374.0,1495.0],[1197.0,654.0]],[[637.0,1563.0],[1801.0,1661.0],[482.0,594.0],[1104.0,1209.0]],[[33.0,39.0],[543.0,1554.0],[414.0,1882.0],[124.0,1769.0]],[[1729.0,1130.0],[1516.0,1672.0],[1663.0,1892.0],[218.0,406.0]],[[1928.0,153.0],[2.0,172.0],[455.0,571.0],[1459.0,1109.0]],[[1459.0,1941.0],[1004.0,982.0],[432.0,1465.0],[649.0,476.0]],[[166.0,1284.0],[1730.0,1418.0],[1038.0,228.0],[1781.0,1699.0]],[[1541.0,1469.0],[1203.0,1397.0],[1806.0,975.0],[591.0,229.0]],[[1398.0,464.0],[705.0,1996.0],[1396.0,497.0],[88.0,1967.0]],[[856.0,1569.0],[715.0,1627.0],[933.0,408.0],[1017.0,1374.0]],[[1347.0,1004.0],[1889.0,1929.0],[1513.0,2017.0],[793.0,1769.0]],[[1804.0,1633.0],[493.0,1999.0],[1091.0,512.0],[613.0,48.0]],[[1540.0,1698.0],[446.0,107.0],[305.0,749.0],[1879.0,1544.0]],[[1181.0,636.0],[631.0,433.0],[1042.0,76.0],[1902.0,1624.0]],[[935.0,1600.0],[21.0,1021.0],[1732.0,650.0],[733.0,1402.0]],[[979.0,311.0],[659.0,719.0],[1538.0,88.0],[888.0,1750.0]],[[965.0,165.0],[779.0,316.0],[1015.0,1630.0],[1904.0,487.0]],[[198.0,1585.0],[367.0,387.0],[1961.0,184.0],[979.0,49.0]],[[85.0,1277.0],[1910.0,1138.0],[1702.0,682.0],[545.0,1303.0]],[[1837.0,1710.0],[686.0,1619.0],[1593.0,822.0],[2029.0,1140.0]],[[1474.0,620.0],[1062.0,1144.0],[717.0,342.0],[1476.0,1376.0]],[[584.0,1058.0],[1044.0,1033.0],[1430.0,1573.0],[1143.0,1915.0]],[[55.0,610.0],[533.0,1035.0],[925.0,804.0],[288.0,812.0]],[[1758.0,982.0],[570.0,1886.0],[1602.0,802.0],[338.0,316.0]],[[627.0,235.0],[123.0,1660.0],[1567.0,1709.0],[563.0,529.0]],[[303.0,988.0],[1563.0,571.0],[1170.0,829.0],[1626.0,1461.0]],[[730.0,922.0],[1219.0,589.0],[1424.0,2015.0],[1195.0,362.0]],[[1224.0,855.0],[1898.0,89.0],[1189.0,422.0],[1526.0,1816.0]],[[1044.0,238.0],[213.0,1292.0],[654.0,542.0],[423.0,460.0]],[[1782.0,1007.0],[851.0,1625.0],[497.0,869.0],[1572.0,548.0]],[[1042.0,14.0],[495.0,825.0],[1548.0,1974.0],[944.0,1096.0]],[[154.0,687.0],[954.0,1681.0],[1121.0,1725.0],[1632.0,1114.0]],[[2023.0,400.0],[530.0,764.0],[65.0,1859.0],[183.0,2000.0]],[[877.0,1613.0],[1377.0,997.0],[385.0,315.0],[174.0,1731.0]],[[1809.0,773.0],[709.0,778.0],[1576.0,1476.0],[807.0,953.0]],[[1473.0,264.0],[1396.0,212.0],[1877.0,181.0],[724.0,604.0]],[[1169.0,1921.0],[176.0,265.0],[1623.0,376.0],[1638.0,1234.0]],[[1615.0,1097.0],[1442.0,1927.0],[201.0,1954.0],[71.0,1748.0]],[[1247.0,1299.0],[611.0,1137.0],[269.0,1478.0],[1700.0,1601.0]],[[96.0,464.0],[151.0,58.0],[413.0,1360.0],[1379.0,1508.0]],[[141.0,1516.0],[303.0,1986.0],[343.0,1827.0],[1370.0,2048.0]],[[13.0,658.0],[1331.0,1478.0],[876.0,598.0],[607.0,441.0]],[[1654.0,1299.0],[1723.0,1474.0],[1398.0,1064.0],[1509.0,154.0]],[[259.0,1010.0],[1087.0,1626.0],[1162.0,341.0],[306.0,697.0]],[[1094.0,1694.0],[341.0,517.0],[1156.0,1076.0],[961.0,862.0]],[[404.0,1135.0],[1967.0,192.0],[1234.0,835.0],[307.0,1292.0]],[[1391.0,1212.0],[545.0,144.0],[1811.0,1490.0],[152.0,117.0]],[[1292.0,1710.0],[670.0,166.0],[1739.0,755.0],[808.0,953.0]],[[470.0,532.0],[501.0,1091.0],[1877.0,804.0],[226.0,1479.0]],[[1868.0,1371.0],[1452.0,900.0],[38.0,57.0],[2001.0,132.0]],[[673.0,1037.0],[163.0,37.0],[942.0,346.0],[709.0,143.0]],[[820.0,857.0],[1814.0,1182.0],[995.0,2009.0],[1521.0,1330.0]],[[1605.0,300.0],[799.0,743.0],[768.0,1216.0],[1745.0,1941.0]],[[1488.0,94.0],[1996.0,84.0],[429.0,1771.0],[1407.0,1388.0]],[[303.0,1721.0],[799.0,2024.0],[1956.0,1843.0],[1929.0,677.0]],[[1098.0,1235.0],[1623.0,1061.0],[1046.0,1270.0],[60.0,187.0]],[[1874.0,1874.0],[1456.0,950.0],[1819.0,856.0],[1949.0,1374.0]],[[593.0,1572.0],[1791.0,222.0],[455.0,1459.0],[33.0,1047.0]],[[221.0,1255.0],[1551.0,61.0],[1329.0,1385.0],[1264.0,203.0]],[[854.0,334.0],[1346.0,491.0],[271.0,525.0],[1205.0,1677.0]],[[1395.0,952.0],[111.0,749.0],[1498.0,1239.0],[1203.0,1548.0]],[[1722.0,1890.0],[303.0,815.0],[1669.0,948.0],[172.0,986.0]],[[919.0,997.0],[1616.0,1553.0],[860.0,622.0],[1225.0,1474.0]],[[5.0,1258.0],[1819.0,2039.0],[699.0,599.0],[127.0,1518.0]],[[1789.0,1400.0],[2005.0,1300.0],[456.0,1197.0],[1130.0,1759.0]],[[46.0,1272.0],[354.0,2014.0],[470.0,903.0],[1084.0,1789.0]],[[1526.0,944.0],[222.0,419.0],[667.0,531.0],[1196.0,197.0]],[[279.0,893.0],[12.0,253.0],[1732.0,86.0],[271.0,225.0]],[[36.0,142.0],[1389.0,1362.0],[76.0,36.0],[865.0,1920.0]],[[819.0,1090.0],[1209.0,1029.0],[956.0,748.0],[863.0,1603.0]],[[244.0,977.0],[1853.0,144.0],[1357.0,1338.0],[1666.0,490.0]],[[65.0,757.0],[383.0,757.0],[894.0,921.0],[723.0,1245.0]],[[400.0,240.0],[1285.0,599.0],[257.0,1815.0],[614.0,945.0]],[[176.0,1172.0],[1410.0,238.0],[365.0,1812.0],[820.0,933.0]],[[758.0,488.0],[235.0,828.0],[221.0,474.0],[358.0,900.0]],[[1171.0,1032.0],[1731.0,1018.0],[132.0,1031.0],[797.0,1334.0]],[[1433.0,1463.0],[1860.0,1566.0],[1583.0,366.0],[1745.0,1001.0]],[[2004.0,1407.0],[731.0,466.0],[981.0,296.0],[1788.0,1134.0]],[[1244.0,1372.0],[1517.0,1676.0],[1869.0,1492.0],[1441.0,1293.0]],[[1622.0,1930.0],[70.0,1516.0],[521.0,1238.0],[688.0,1237.0]],[[519.0,612.0],[683.0,1874.0],[623.0,553.0],[659.0,326.0]],[[1039.0,964.0],[1457.0,1291.0],[702.0,1135.0],[1937.0,1268.0]],[[316.0,1754.0],[630.0,1446.0],[1841.0,440.0],[638.0,1293.0]],[[283.0,765.0],[1964.0,143.0],[191.0,785.0],[1458.0,1499.0]],[[1455.0,1534.0],[1401.0,493.0],[756.0,1537.0],[133.0,1109.0]],[[860.0,255.0],[1011.0,1246.0],[1339.0,1650.0],[1000.0,1473.0]],[[202.0,949.0],[1190.0,27.0],[800.0,397.0],[554.0,912.0]],[[1510.0,1091.0],[576.0,665.0],[934.0,308.0],[1275.0,1769.0]],[[1799.0,1945.0],[749.0,1456.0],[800.0,1773.0],[303.0,1134.0]],[[840.0,937.0],[582.0,547.0],[852.0,86.0],[670.0,1989.0]],[[1486.0,753.0],[201.0,1475.0],[337.0,972.0],[865.0,356.0]],[[1807.0,804.0],[1402.0,675.0],[73.0,891.0],[1294.0,1967.0]],[[148.0,214.0],[1502.0,2047.0],[1431.0,555.0],[1999.0,279.0]],[[1305.0,1276.0],[1301.0,366.0],[1969.0,1384.0],[1702.0,292.0]],[[1073.0,257.0],[1322.0,78.0],[738.0,1341.0],[924.0,1282.0]],[[1075.0,1033.0],[1254.0,1997.0],[1703.0,49.0],[1206.0,665.0]],[[1191.0,199.0],[474.0,1767.0],[1763.0,890.0],[1139.0,1460.0]],[[2024.0,1152.0],[1048.0,706.0],[1321.0,584.0],[1440.0,387.0]],[[1626.0,1461.0],[787.0,1621.0],[1840.0,614.0],[1970.0,994.0]],[[154.0,1014.0],[323.0,288.0],[157.0,1931.0],[1983.0,1340.0]],[[698.0,2036.0],[1628.0,54.0],[1581.0,1845.0],[677.0,1528.0]],[[211.0,1508.0],[1445.0,1793.0],[972.0,1243.0],[361.0,1809.0]],[[1462.0,799.0],[660.0,551.0],[1811.0,184.0],[1491.0,1381.0]],[[710.0,2008.0],[1959.0,34.0],[958.0,243.0],[1819.0,669.0]],[[853.0,1639.0],[1908.0,505.0],[1289.0,1073.0],[566.0,693.0]],[[1351.0,539.0],[739.0,1262.0],[959.0,1750.0],[1917.0,1875.0]],[[1274.0,695.0],[1264.0,846.0],[1157.0,633.0],[26.0,1394.0]],[[487.0,1742.0],[1556.0,732.0],[1800.0,1840.0],[1811.0,1489.0]],[[845.0,221.0],[348.0,439.0],[398.0,1587.0],[562.0,1816.0]],[[1626.0,745.0],[1945.0,1838.0],[149.0,794.0],[1843.0,2000.0]],[[1596.0,1190.0],[1428.0,710.0],[1119.0,738.0],[112.0,248.0]],[[265.0,941.0],[1825.0,1306.0],[1808.0,1373.0],[416.0,1590.0]],[[220.0,1918.0],[1139.0,1676.0],[1905.0,1356.0],[393.0,672.0]],[[1643.0,1749.0],[1956.0,610.0],[1308.0,597.0],[1433.0,562.0]],[[792.0,921.0],[885.0,1859.0],[637.0,423.0],[421.0,1741.0]],[[215.0,1857.0],[621.0,1534.0],[1317.0,1147.0],[1630.0,58.0]],[[1587.0,1995.0],[1824.0,1235.0],[1241.0,1585.0],[1282.0,1186.0]],[[713.0,410.0],[2004.0,736.0],[1825.0,628.0],[1878.0,432.0]],[[505.0,1304.0],[1295.0,2024.0],[1396.0,1309.0],[1894.0,1324.0]],[[1984.0,1614.0],[893.0,680.0],[987.0,819.0],[1004.0,211.0]],[[1314.0,252.0],[1344.0,1719.0],[121.0,1410.0],[1472.0,1480.0]],[[1674.0,856.0],[1182.0,919.0],[1284.0,1627.0],[1575.0,719.0]],[[34.0,1592.0],[1434.0,910.0],[958.0,269.0],[1311.0,1575.0]],[[834.0,1202.0],[392.0,1777.0],[16.0,1437.0],[381.0,1670.0]],[[627.0,456.0],[734.0,1394.0],[590.0,1538.0],[1789.0,1333.0]],[[1135.0,854.0],[794.0,648.0],[674.0,657.0],[600.0,490.0]],[[1810.0,532.0],[1766.0,548.0],[1367.0,1299.0],[561.0,84.0]],[[1468.0,713.0],[926.0,962.0],[2035.0,2001.0],[140.0,367.0]],[[547.0,1920.0],[584.0,856.0],[1476.0,564.0],[1147.0,1427.0]],[[265.0,1571.0],[1946.0,122.0],[1891.0,806.0],[986.0,844.0]],[[20.0,1245.0],[172.0,1093.0],[775.0,294.0],[433.0,451.0]],[[1639.0,1359.0],[429.0,1824.0],[1977.0,1149.0],[584.0,1766.0]],[[1521.0,1429.0],[1571.0,1685.0],[1786.0,1507.0],[843.0,801.0]],[[267.0,593.0],[974.0,982.0],[85.0,987.0],[1612.0,1870.0]],[[1805.0,390.0],[221.0,705.0],[31.0,181.0],[1762.0,1140.0]],[[1701.0,543.0],[965.0,1533.0],[1698.0,1400.0],[193.0,1861.0]],[[529.0,1491.0],[246.0,1430.0],[480.0,1005.0],[510.0,1788.0]],[[609.0,78.0],[1496.0,532.0],[616.0,1180.0],[101.0,1934.0]],[[109.0,1978.0],[274.0,1765.0],[376.0,1924.0],[396.0,527.0]],[[1612.0,1679.0],[990.0,1555.0],[1956.0,1299.0],[1793.0,478.0]],[[275.0,862.0],[1512.0,427.0],[393.0,1453.0],[432.0,802.0]],[[455.0,358.0],[14.0,1768.0],[960.0,374.0],[1258.0,1997.0]],[[253.0,1757.0],[1221.0,1605.0],[167.0,118.0],[1133.0,1959.0]],[[1793.0,896.0],[1100.0,1317.0],[1956.0,1808.0],[224.0,1101.0]],[[711.0,1793.0],[1865.0,1211.0],[747.0,1314.0],[1629.0,1694.0]],[[1631.0,1955.0],[903.0,1254.0],[70.0,258.0],[605.0,2021.0]],[[474.0,1472.0],[1061.0,1266.0],[1241.0,567.0],[437.0,565.0]],[[1864.0,155.0],[1825.0,1923.0],[1334.0,1520.0],[512.0,59.0]],[[825.0,1100.0],[265.0,1892.0],[1160.0,49.0],[1089.0,88.0]],[[1644.0,458.0],[400.0,1319.0],[1832.0,374.0],[2041.0,1407.0]],[[178.0,769.0],[694.0,227.0],[476.0,174.0],[480.0,1249.0]],[[821.0,663.0],[615.0,933.0],[890.0,367.0],[1445.0,1783.0]],[[1092.0,551.0],[1171.0,1016.0],[284.0,1084.0],[232.0,89.0]],[[1768.0,1156.0],[1944.0,1728.0],[1787.0,278.0],[758.0,879.0]],[[139.0,1758.0],[1697.0,1453.0],[1453.0,607.0],[732.0,925.0]],[[939.0,243.0],[1497.0,274.0],[1828.0,1318.0],[891.0,897.0]],[[1055.0,634.0],[1562.0,439.0],[1956.0,7.0],[1933.0,1278.0]],[[1075.0,1192.0],[854.0,543.0],[1558.0,143.0],[1566.0,1872.0]],[[99.0,539.0],[948.0,2020.0],[405.0,1212.0],[1786.0,822.0]],[[1367.0,402.0],[1019.0,993.0],[2013.0,474.0],[728.0,2033.0]],[[1468.0,1774.0],[1639.0,1727.0],[97.0,1635.0],[579.0,1743.0]],[[521.0,248.0],[1197.0,1591.0],[1761.0,390.0],[825.0,1111.0]],[[1961.0,1729.0],[1082.0,436.0],[1334.0,626.0],[1063.0,100.0]],[[397.0,1518.0],[1860.0,1085.0],[387.0,1163.0],[569.0,346.0]],[[1664.0,1558.0],[114.0,1961.0],[532.0,1603.0],[2015.0,954.0]],[[115.0,1540.0],[253.0,1681.0],[344.0,1023.0],[162.0,1860.0]],[[343.0,1202.0],[162.0,1423.0],[173.0,279.0],[299.0,185.0]],[[1256.0,1451.0],[1261.0,371.0],[1927.0,1464.0],[1338.0,700.0]],[[1454.0,1023.0],[1340.0,953.0],[1017.0,891.0],[1272.0,1253.0]],[[1322.0,1236.0],[19.0,1970.0],[1035.0,942.0],[604.0,989.0]],[[660.0,346.0],[1063.0,1633.0],[829.0,564.0],[675.0,303.0]],[[1295.0,1581.0],[864.0,648.0],[158.0,1825.0],[884.0,1641.0]],[[461.0,1273.0],[900.0,1186.0],[1826.0,1378.0],[341.0,280.0]],[[288.0,945.0],[490.0,1898.0],[1877.0,40.0],[686.0,1876.0]],[[1772.0,449.0],[787.0,63.0],[996.0,1260.0],[877.0,1204.0]],[[1261.0,1081.0],[1431.0,1088.0],[1177.0,194.0],[119.0,43.0]],[[1807.0,173.0],[844.0,315.0],[1292.0,1852.0],[1246.0,468.0]],[[1010.0,454.0],[790.0,123.0],[797.0,555.0],[105.0,1803.0]],[[117.0,931.0],[1946.0,708.0],[36.0,917.0],[566.0,256.0]],[[65.0,561.0],[1312.0,346.0],[1068.0,798.0],[1631.0,32.0]],[[1145.0,1441.0],[1060.0,1578.0],[1654.0,1906.0],[1142.0,362.0]],[[737.0,1963.0],[1613.0,545.0],[853.0,105.0],[211.0,1299.0]],[[592.0,896.0],[1305.0,1625.0],[167.0,1672.0],[1944.0,265.0]],[[140.0,537.0],[1682.0,1595.0],[1112.0,181.0],[891.0,795.0]],[[1246.0,1557.0],[1227.0,93.0],[1092.0,781.0],[665.0,941.0]],[[360.0,863.0],[1967.0,674.0],[215.0,1648.0],[1158.0,60.0]],[[618.0,405.0],[162.0,1761.0],[1945.0,717.0],[893.0,1919.0]],[[441.0,1658.0],[917.0,259.0],[520.0,1384.0],[1944.0,2021.0]],[[1505.0,1772.0],[1016.0,1814.0],[1064.0,1655.0],[1457.0,1582.0]],[[937.0,1545.0],[435.0,740.0],[1411.0,310.0],[105.0,1718.0]],[[2023.0,249.0],[1885.0,451.0],[959.0,1862.0],[1438.0,374.0]],[[1372.0,151.0],[1133.0,1372.0],[531.0,684.0],[1760.0,1276.0]],[[1813.0,996.0],[2004.0,1571.0],[113.0,1043.0],[493.0,1174.0]],[[1063.0,101.0],[345.0,1329.0],[741.0,896.0],[1202.0,331.0]],[[698.0,1865.0],[1523.0,1633.0],[1854.0,1933.0],[420.0,2001.0]],[[341.0,139.0],[242.0,76.0],[1141.0,149.0],[1100.0,1273.0]],[[722.0,1955.0],[1381.0,69.0],[1862.0,1400.0],[972.0,927.0]],[[1416.0,237.0],[93.0,1804.0],[811.0,1612.0],[627.0,734.0]],[[950.0,333.0],[1617.0,167.0],[713.0,1311.0],[19.0,1860.0]],[[665.0,156.0],[1730.0,909.0],[1053.0,1793.0],[773.0,166.0]],[[1546.0,1681.0],[1632.0,1745.0],[1113.0,1813.0],[1380.0,97.0]],[[320.0,1935.0],[1717.0,663.0],[1763.0,656.0],[704.0,1094.0]],[[1691.0,1971.0],[1169.0,1422.0],[1870.0,1628.0],[1542.0,1172.0]],[[983.0,1469.0],[923.0,1084.0],[82.0,296.0],[1078.0,1596.0]],[[657.0,1081.0],[1033.0,2009.0],[64.0,652.0],[1980.0,452.0]],[[899.0,622.0],[462.0,1574.0],[232.0,706.0],[279.0,388.0]],[[1918.0,1918.0],[102.0,237.0],[1111.0,210.0],[1934.0,851.0]],[[1457.0,1793.0],[452.0,1387.0],[1304.0,1565.0],[1593.0,1188.0]],[[338.0,938.0],[1807.0,1431.0],[1750.0,1766.0],[1785.0,1091.0]],[[764.0,617.0],[216.0,1353.0],[1440.0,1542.0],[275.0,1302.0]],[[725.0,595.0],[469.0,836.0],[1954.0,954.0],[1468.0,661.0]],[[832.0,1224.0],[703.0,566.0],[1638.0,1743.0],[2003.0,1437.0]],[[139.0,308.0],[99.0,1507.0],[1019.0,637.0],[874.0,1622.0]],[[1817.0,1117.0],[1745.0,1384.0],[1974.0,1395.0],[332.0,224.0]],[[570.0,1924.0],[721.0,372.0],[33.0,266.0],[98.0,751.0]],[[1141.0,795.0],[1886.0,1647.0],[1111.0,1081.0],[1574.0,431.0]],[[1619.0,1897.0],[988.0,291.0],[1280.0,550.0],[108.0,1550.0]],[[230.0,1189.0],[1413.0,69.0],[1798.0,1299.0],[41.0,1293.0]],[[1605.0,209.0],[1829.0,398.0],[1734.0,1663.0],[504.0,71.0]],[[47.0,1673.0],[1426.0,719.0],[1656.0,166.0],[586.0,1169.0]],[[1683.0,680.0],[1921.0,1202.0],[1049.0,143.0],[1600.0,1687.0]],[[599.0,1327.0],[697.0,1856.0],[1610.0,514.0],[323.0,1609.0]],[[1066.0,1604.0],[2003.0,137.0],[1190.0,653.0],[1101.0,1590.0]],[[1122.0,511.0],[1046.0,36.0],[489.0,437.0],[1916.0,619.0]],[[1908.0,986.0],[973.0,170.0],[920.0,327.0],[443.0,395.0]],[[153.0,467.0],[179.0,1033.0],[1699.0,600.0],[1420.0,467.0]],[[204.0,1595.0],[915.0,652.0],[2004.0,702.0],[1442.0,1630.0]],[[701.0,1335.0],[288.0,203.0],[62.0,1220.0],[407.0,1845.0]],[[356.0,2.0],[194.0,1151.0],[1249.0,1042.0],[1878.0,1569.0]],[[480.0,915.0],[1253.0,514.0],[98.0,1499.0],[1828.0,386.0]],[[1765.0,636.0],[1125.0,466.0],[1528.0,1033.0],[864.0,1345.0]],[[577.0,913.0],[30.0,942.0],[1976.0,1469.0],[521.0,1672.0]],[[1404.0,1750.0],[1802.0,458.0],[1025.0,217.0],[1209.0,1305.0]],[[815.0,852.0],[939.0,991.0],[1540.0,1421.0],[1050.0,6.0]],[[2015.0,575.0],[1751.0,1981.0],[370.0,1130.0],[409.0,898.0]],[[444.0,1745.0],[1659.0,582.0],[469.0,1800.0],[886.0,660.0]],[[882.0,1116.0],[1497.0,1337.0],[1422.0,1031.0],[611.0,127.0]],[[906.0,1053.0],[1974.0,65.0],[1400.0,68.0],[714.0,822.0]],[[1063.0,942.0],[299.0,1745.0],[1511.0,1516.0],[776.0,432.0]],[[18.0,1606.0],[1388.0,1350.0],[1680.0,1405.0],[1054.0,1648.0]],[[1129.0,1446.0],[307.0,1791.0],[913.0,1933.0],[1417.0,1158.0]],[[119.0,434.0],[220.0,700.0],[922.0,1799.0],[1203.0,1733.0]],[[1632.0,18.0],[278.0,1625.0],[625.0,850.0],[1942.0,1612.0]],[[2022.0,404.0],[1679.0,675.0],[2018.0,880.0],[1264.0,148.0]],[[1223.0,1200.0],[568.0,1028.0],[1244.0,1949.0],[546.0,1787.0]],[[1365.0,1315.0],[863.0,1138.0],[162.0,1272.0],[1206.0,2036.0]],[[1224.0,1082.0],[654.0,1186.0],[1077.0,1368.0],[610.0,1060.0]],[[1586.0,1810.0],[2027.0,690.0],[1571.0,162.0],[379.0,842.0]],[[1298.0,209.0],[1251.0,164.0],[1701.0,445.0],[1326.0,529.0]],[[42.0,1410.0],[988.0,1451.0],[1779.0,986.0],[342.0,133.0]],[[1371.0,77.0],[1816.0,106.0],[690.0,1151.0],[857.0,1756.0]],[[1184.0,675.0],[179.0,159.0],[2036.0,1598.0],[456.0,1556.0]],[[1179.0,1786.0],[204.0,938.0],[1366.0,1717.0],[1994.0,832.0]],[[364.0,1378.0],[1657.0,734.0],[964.0,1987.0],[295.0,1716.0]],[[1618.0,893.0],[1047.0,6.0],[1154.0,133.0],[1065.0,349.0]],[[736.0,1034.0],[1838.0,1780.0],[1251.0,411.0],[1217.0,220.0]],[[1954.0,719.0],[1042.0,855.0],[516.0,172.0],[1635.0,40.0]],[[1216.0,11.0],[1563.0,1392.0],[396.0,1051.0],[663.0,818.0]],[[297.0,716.0],[1544.0,95.0],[902.0,1663.0],[73.0,17.0]],[[1702.0,719.0],[218.0,1621.0],[1697.0,781.0],[651.0,909.0]],[[375.0,1851.0],[1369.0,1024.0],[785.0,1047.0],[1591.0,1013.0]],[[1197.0,1055.0],[602.0,1089.0],[1481.0,1128.0],[896.0,775.0]],[[89.0,431.0],[895.0,1129.0],[691.0,1319.0],[899.0,666.0]],[[134.0,897.0],[1588.0,1072.0],[1054.0,869.0],[1070.0,1538.0]],[[171.0,135.0],[630.0,2035.0],[1788.0,1234.0],[1501.0,1661.0]],[[1458.0,810.0],[1173.0,1147.0],[1082.0,1979.0],[625.0,1460.0]],[[579.0,1596.0],[250.0,295.0],[1065.0,308.0],[2021.0,852.0]],[[1858.0,1259.0],[164.0,1101.0],[1380.0,17.0],[2029.0,1767.0]],[[1766.0,1735.0],[1501.0,1990.0],[792.0,1771.0],[1603.0,1184.0]],[[393.0,331.0],[659.0,1381.0],[1496.0,1758.0],[1564.0,504.0]],[[1541.0,212.0],[1776.0,823.0],[461.0,938.0],[1956.0,1571.0]],[[708.0,545.0],[901.0,392.0],[1420.0,1323.0],[1800.0,682.0]],[[1549.0,1983.0],[754.0,153.0],[812.0,1023.0],[537.0,489.0]],[[1121.0,53.0],[20.0,1523.0],[1169.0,879.0],[222.0,1277.0]],[[606.0,521.0],[275.0,640.0],[1712.0,1099.0],[535.0,298.0]],[[791.0,658.0],[1707.0,887.0],[1608.0,2007.0],[709.0,263.0]],[[2041.0,994.0],[835.0,298.0],[621.0,1001.0],[775.0,603.0]],[[1063.0,267.0],[1524.0,323.0],[1456.0,1141.0],[734.0,1923.0]],[[1775.0,904.0],[516.0,481.0],[1783.0,1755.0],[1506.0,917.0]],[[1817.0,1604.0],[1362.0,751.0],[214.0,199.0],[1504.0,1897.0]],[[661.0,1911.0],[1528.0,1442.0],[639.0,1873.0],[823.0,1954.0]],[[1155.0,852.0],[520.0,902.0],[1157.0,433.0],[360.0,929.0]],[[1738.0,877.0],[1231.0,2016.0],[252.0,1538.0],[835.0,218.0]],[[1265.0,1226.0],[863.0,1755.0],[51.0,1870.0],[1333.0,1719.0]],[[999.0,431.0],[690.0,208.0],[1565.0,667.0],[70.0,2031.0]],[[1987.0,1510.0],[1747.0,117.0],[1785.0,1594.0],[972.0,33.0]],[[176.0,818.0],[1292.0,146.0],[552.0,1796.0],[731.0,1851.0]],[[599.0,588.0],[1030.0,1564.0],[380.0,505.0],[290.0,1570.0]],[[1982.0,1501.0],[862.0,154.0],[1676.0,903.0],[1970.0,809.0]],[[691.0,969.0],[807.0,1386.0],[1223.0,1955.0],[963.0,872.0]],[[1153.0,504.0],[41.0,150.0],[1291.0,364.0],[731.0,1868.0]],[[329.0,1678.0],[574.0,177.0],[574.0,1309.0],[1469.0,1851.0]],[[780.0,1639.0],[1898.0,351.0],[1615.0,1498.0],[40.0,1167.0]],[[866.0,1458.0],[1533.0,30.0],[388.0,1824.0],[1660.0,1256.0]],[[709.0,1225.0],[898.0,1351.0],[1361.0,867.0],[167.0,208.0]],[[76.0,745.0],[1894.0,1328.0],[150.0,1089.0],[1112.0,269.0]],[[1023.0,48.0],[532.0,1692.0],[1423.0,1105.0],[2041.0,159.0]],[[1580.0,453.0],[1250.0,1717.0],[1016.0,939.0],[1785.0,1081.0]],[[42.0,23.0],[620.0,1945.0],[610.0,1433.0],[317.0,1013.0]],[[632.0,543.0],[1642.0,568.0],[1352.0,795.0],[530.0,560.0]],[[477.0,521.0],[136.0,1139.0],[1118.0,1443.0],[30.0,595.0]],[[35.0,261.0],[1868.0,1772.0],[1628.0,1267.0],[547.0,1604.0]],[[1745.0,1474.0],[1834.0,1432.0],[1197.0,1946.0],[760.0,1101.0]],[[32.0,1275.0],[941.0,217.0],[2045.0,244.0],[30.0,324.0]],[[1897.0,10.0],[1005.0,542.0],[1605.0,1591.0],[911.0,1133.0]],[[750.0,893.0],[661.0,371.0],[1353.0,1420.0],[352.0,499.0]],[[945.0,838.0],[1345.0,1834.0],[339.0,1766.0],[1459.0,676.0]],[[741.0,416.0],[1451.0,756.0],[2020.0,324.0],[1868.0,1766.0]],[[895.0,259.0],[277.0,1030.0],[1344.0,1605.0],[1508.0,1336.0]],[[1762.0,289.0],[779.0,1686.0],[1423.0,2036.0],[1419.0,488.0]],[[1832.0,1348.0],[35.0,922.0],[1245.0,1682.0],[573.0,784.0]],[[1139.0,189.0],[664.0,1269.0],[233.0,455.0],[1088.0,457.0]],[[736.0,1852.0],[1015.0,1919.0],[1765.0,223.0],[577.0,2018.0]],[[1473.0,1195.0],[1537.0,364.0],[1790.0,531.0],[955.0,1753.0]],[[1993.0,256.0],[1481.0,758.0],[253.0,835.0],[776.0,76.0]],[[1421.0,973.0],[978.0,1682.0],[1699.0,676.0],[961.0,3.0]],[[899.0,232.0],[636.0,369.0],[101.0,582.0],[1091.0,940.0]],[[1477.0,1362.0],[564.0,384.0],[1047.0,1690.0],[1456.0,173.0]],[[254.0,1877.0],[154.0,1288.0],[1275.0,1229.0],[1596.0,1256.0]],[[1576.0,1976.0],[1206.0,491.0],[46.0,433.0],[1736.0,301.0]],[[854.0,504.0],[38.0,1006.0],[1921.0,290.0],[871.0,1396.0]],[[879.0,1212.0],[1170.0,1905.0],[1905.0,861.0],[1895.0,1542.0]],[[342.0,126.0],[302.0,1232.0],[1840.0,839.0],[1209.0,1697.0]],[[757.0,1616.0],[1536.0,1896.0],[906.0,1010.0],[2022.0,57.0]],[[1198.0,1099.0],[1942.0,2021.0],[1441.0,448.0],[491.0,876.0]],[[1828.0,1589.0],[880.0,1724.0],[252.0,720.0],[1583.0,1741.0]],[[1530.0,580.0],[278.0,654.0],[184.0,868.0],[1905.0,1269.0]],[[1232.0,1947.0],[551.0,86.0],[1848.0,1776.0],[1413.0,1784.0]],[[1488.0,892.0],[1106.0,832.0],[1888.0,1940.0],[1111.0,1701.0]],[[1165.0,1134.0],[1817.0,269.0],[455.0,1332.0],[1833.0,1176.0]],[[929.0,1311.0],[1016.0,635.0],[643.0,1041.0],[1016.0,1686.0]],[[123.0,1758.0],[1630.0,923.0],[557.0,294.0],[347.0,676.0]],[[1919.0,1554.0],[900.0,1196.0],[1602.0,1099.0],[32.0,1162.0]],[[613.0,492.0],[1765.0,1153.0],[1232.0,1714.0],[250.0,609.0]],[[398.0,678.0],[1996.0,1773.0],[454.0,235.0],[1469.0,1303.0]],[[1258.0,147.0],[1254.0,1848.0],[128.0,1409.0],[1165.0,870.0]],[[1079.0,1121.0],[691.0,1175.0],[1385.0,367.0],[145.0,527.0]],[[538.0,1571.0],[1323.0,1388.0],[1929.0,685.0],[1199.0,111.0]],[[1086.0,72.0],[94.0,1721.0],[1900.0,99.0],[1593.0,435.0]],[[509.0,67.0],[1571.0,366.0],[2045.0,879.0],[1449.0,133.0]],[[1678.0,1933.0],[1307.0,775.0],[34.0,520.0],[1932.0,1928.0]],[[1026.0,1770.0],[412.0,1704.0],[1853.0,1208.0],[381.0,213.0]],[[1735.0,630.0],[1437.0,811.0],[339.0,1869.0],[1481.0,453.0]],[[1356.0,412.0],[833.0,1301.0],[649.0,657.0],[1353.0,334.0]],[[854.0,1201.0],[269.0,2009.0],[1889.0,2048.0],[1820.0,1581.0]],[[1491.0,1945.0],[714.0,583.0],[1.0,717.0],[1263.0,725.0]],[[614.0,849.0],[550.0,998.0],[1873.0,565.0],[347.0,1984.0]],[[1595.0,1615.0],[1737.0,1786.0],[1927.0,1147.0],[1955.0,513.0]],[[810.0,1548.0],[144.0,1123.0],[575.0,1802.0],[891.0,635.0]],[[1598.0,1811.0],[253.0,1423.0],[925.0,639.0],[1170.0,2005.0]],[[1354.0,627.0],[264.0,1611.0],[299.0,303.0],[0.0,123.0]],[[307.0,357.0],[536.0,1030.0],[251.0,853.0],[1764.0,1383.0]],[[1140.0,1455.0],[820.0,649.0],[1678.0,328.0],[1451.0,472.0]],[[1731.0,1846.0],[1366.0,416.0],[42.0,229.0],[607.0,1670.0]],[[864.0,826.0],[279.0,656.0],[1865.0,97.0],[1339.0,1206.0]],[[1271.0,628.0],[1852.0,214.0],[179.0,1180.0],[652.0,99.0]],[[1293.0,97.0],[605.0,1032.0],[436.0,956.0],[1050.0,2026.0]],[[2044.0,785.0],[316.0,536.0],[1169.0,123.0],[966.0,645.0]],[[716.0,997.0],[1884.0,440.0],[14.0,801.0],[1497.0,698.0]],[[1106.0,388.0],[330.0,1230.0],[950.0,1543.0],[1219.0,555.0]],[[1234.0,570.0],[1219.0,493.0],[1226.0,411.0],[854.0,1802.0]],[[1636.0,400.0],[124.0,1619.0],[1952.0,31.0],[1203.0,1922.0]],[[1920.0,1511.0],[737.0,857.0],[1954.0,817.0],[927.0,564.0]],[[1772.0,813.0],[1493.0,2012.0],[933.0,199.0],[1006.0,470.0]],[[1487.0,273.0],[174.0,870.0],[1769.0,1356.0],[1667.0,1840.0]],[[1809.0,1885.0],[1839.0,1520.0],[160.0,833.0],[1063.0,533.0]],[[443.0,1700.0],[819.0,1341.0],[422.0,21.0],[914.0,812.0]],[[1573.0,798.0],[1241.0,1265.0],[1533.0,953.0],[115.0,1022.0]],[[1136.0,1249.0],[742.0,468.0],[60.0,1425.0],[632.0,1595.0]],[[2011.0,1864.0],[460.0,925.0],[1438.0,252.0],[336.0,988.0]],[[703.0,808.0],[1751.0,575.0],[1550.0,1642.0],[1442.0,336.0]],[[152.0,1865.0],[1448.0,1179.0],[1524.0,1347.0],[1532.0,45.0]],[[468.0,1584.0],[1235.0,1119.0],[239.0,1965.0],[1132.0,213.0]],[[2013.0,1301.0],[1754.0,1866.0],[901.0,698.0],[168.0,1642.0]],[[1050.0,785.0],[1289.0,824.0],[457.0,685.0],[1722.0,1727.0]],[[1035.0,528.0],[380.0,1056.0],[981.0,1144.0],[753.0,1856.0]],[[1743.0,69.0],[592.0,1250.0],[567.0,581.0],[1685.0,150.0]],[[1913.0,2029.0],[183.0,1584.0],[434.0,1204.0],[1693.0,1897.0]],[[440.0,1750.0],[1681.0,109.0],[1075.0,253.0],[1193.0,1062.0]],[[1332.0,117.0],[573.0,225.0],[894.0,1294.0],[397.0,671.0]],[[1236.0,1691.0],[587.0,324.0],[2030.0,258.0],[1744.0,1497.0]],[[612.0,1886.0],[1663.0,1342.0],[1155.0,2042.0],[318.0,553.0]],[[73.0,360.0],[840.0,1489.0],[1965.0,607.0],[715.0,543.0]],[[1739.0,186.0],[305.0,1458.0],[1277.0,1028.0],[1476.0,1515.0]],[[1270.0,1665.0],[1632.0,1949.0],[1795.0,1508.0],[1335.0,1690.0]],[[620.0,543.0],[1957.0,1025.0],[1035.0,1749.0],[1931.0,198.0]],[[1204.0,2020.0],[1411.0,2027.0],[538.0,1830.0],[593.0,1942.0]],[[619.0,953.0],[1364.0,287.0],[1495.0,696.0],[1720.0,1678.0]],[[1246.0,1105.0],[904.0,41.0],[1965.0,1464.0],[331.0,1076.0]],[[1990.0,1609.0],[1881.0,186.0],[1749.0,1036.0],[1996.0,612.0]],[[1318.0,607.0],[843.0,1561.0],[498.0,454.0],[1316.0,588.0]],[[1921.0,1839.0],[632.0,2023.0],[638.0,249.0],[775.0,1632.0]],[[1369.0,1059.0],[1999.0,1185.0],[151.0,1785.0],[329.0,878.0]],[[109.0,1310.0],[1657.0,1194.0],[1008.0,1229.0],[1768.0,1433.0]],[[1809.0,1328.0],[1137.0,493.0],[1522.0,139.0],[1182.0,832.0]],[[445.0,1058.0],[69.0,1056.0],[1368.0,452.0],[1632.0,1887.0]],[[1207.0,1145.0],[1526.0,591.0],[2016.0,236.0],[427.0,1551.0]],[[1820.0,1282.0],[28.0,1911.0],[963.0,558.0],[1284.0,614.0]],[[835.0,1987.0],[703.0,1152.0],[1411.0,898.0],[849.0,250.0]],[[1861.0,1129.0],[1898.0,1888.0],[1924.0,1495.0],[1749.0,1732.0]],[[279.0,921.0],[1411.0,227.0],[1331.0,425.0],[1571.0,2047.0]],[[1355.0,583.0],[831.0,280.0],[1098.0,2035.0],[1992.0,1919.0]],[[1305.0,1906.0],[328.0,2017.0],[1147.0,464.0],[539.0,1706.0]],[[381.0,592.0],[1565.0,394.0],[1735.0,893.0],[600.0,15.0]],[[460.0,415.0],[335.0,1353.0],[509.0,1533.0],[1707.0,1616.0]],[[305.0,1695.0],[1847.0,1229.0],[1785.0,1804.0],[1629.0,546.0]],[[1872.0,1958.0],[1585.0,434.0],[1360.0,1136.0],[578.0,1084.0]],[[178.0,387.0],[681.0,145.0],[18.0,1339.0],[1384.0,168.0]],[[662.0,1727.0],[1798.0,70.0],[1766.0,1645.0],[500.0,1618.0]],[[232.0,112.0],[558.0,983.0],[1990.0,1599.0],[1285.0,525.0]],[[1181.0,570.0],[573.0,578.0],[597.0,970.0],[812.0,125.0]],[[870.0,2005.0],[1875.0,1490.0],[1923.0,1753.0],[2.0,1720.0]],[[977.0,1552.0],[1092.0,109.0],[1326.0,445.0],[1005.0,1755.0]],[[1052.0,529.0],[1916.0,1504.0],[1031.0,496.0],[373.0,1433.0]],[[1658.0,1810.0],[1493.0,1727.0],[1798.0,2046.0],[1542.0,115.0]],[[196.0,1998.0],[1321.0,946.0],[422.0,80.0],[1433.0,315.0]],[[735.0,1946.0],[502.0,946.0],[592.0,1882.0],[309.0,686.0]],[[1143.0,1939.0],[1494.0,765.0],[954.0,178.0],[1197.0,1816.0]],[[1856.0,1154.0],[1006.0,45.0],[1245.0,1925.0],[609.0,812.0]],[[806.0,743.0],[1254.0,295.0],[1051.0,690.0],[1604.0,1238.0]],[[740.0,1861.0],[1630.0,383.0],[1463.0,1461.0],[944.0,11.0]],[[673.0,1862.0],[515.0,1156.0],[1583.0,608.0],[1379.0,1404.0]],[[1026.0,13.0],[587.0,770.0],[1053.0,872.0],[25.0,237.0]],[[148.0,1881.0],[1165.0,911.0],[449.0,319.0],[612.0,831.0]],[[729.0,127.0],[1784.0,642.0],[703.0,1802.0],[1313.0,212.0]],[[1829.0,1464.0],[1175.0,522.0],[215.0,996.0],[465.0,1852.0]],[[2013.0,941.0],[1296.0,457.0],[1253.0,618.0],[1091.0,1719.0]],[[1085.0,470.0],[74.0,17.0],[1518.0,753.0],[232.0,1340.0]],[[651.0,173.0],[141.0,120.0],[1070.0,1014.0],[125.0,1193.0]],[[1975.0,2045.0],[1383.0,366.0],[817.0,717.0],[1438.0,365.0]],[[1515.0,591.0],[1474.0,794.0],[1891.0,1551.0],[1835.0,1420.0]],[[320.0,1010.0],[955.0,389.0],[966.0,361.0],[1278.0,1514.0]],[[332.0,499.0],[1476.0,1021.0],[1161.0,1268.0],[510.0,685.0]],[[1665.0,867.0],[1900.0,559.0],[806.0,325.0],[1777.0,297.0]],[[1561.0,630.0],[999.0,1234.0],[1049.0,1961.0],[1582.0,480.0]],[[444.0,1472.0],[1860.0,1974.0],[1616.0,869.0],[1365.0,615.0]],[[1099.0,1331.0],[802.0,184.0],[1648.0,1088.0],[1318.0,440.0]],[[543.0,835.0],[1529.0,1926.0],[1329.0,176.0],[225.0,1877.0]],[[549.0,1860.0],[2043.0,873.0],[1139.0,1054.0],[639.0,1163.0]],[[486.0,1301.0],[915.0,1399.0],[1280.0,804.0],[497.0,1702.0]],[[864.0,1722.0],[544.0,363.0],[1591.0,119.0],[1580.0,570.0]],[[1613.0,1416.0],[1624.0,599.0],[381.0,945.0],[817.0,1992.0]],[[1937.0,1551.0],[1301.0,1034.0],[36.0,1067.0],[99.0,1561.0]],[[1610.0,592.0],[698.0,1063.0],[406.0,1666.0],[467.0,1956.0]],[[1654.0,219.0],[1998.0,334.0],[325.0,911.0],[1381.0,1580.0]],[[1406.0,1456.0],[1027.0,989.0],[1678.0,562.0],[906.0,1642.0]],[[190.0,1008.0],[769.0,604.0],[1388.0,767.0],[445.0,853.0]],[[1722.0,215.0],[1361.0,1480.0],[1430.0,1788.0],[933.0,1435.0]],[[1559.0,292.0],[921.0,1916.0],[525.0,1461.0],[1510.0,378.0]],[[1896.0,1712.0],[1546.0,1076.0],[300.0,1875.0],[1912.0,610.0]],[[1079.0,58.0],[1484.0,1735.0],[968.0,1438.0],[1907.0,1357.0]],[[1805.0,47.0],[592.0,984.0],[405.0,922.0],[1116.0,1495.0]],[[917.0,755.0],[1821.0,1465.0],[1506.0,159.0],[747.0,1592.0]],[[1447.0,1423.0],[1674.0,1237.0],[1066.0,1877.0],[336.0,218.0]],[[424.0,1103.0],[1268.0,870.0],[1749.0,819.0],[1006.0,1142.0]],[[376.0,1708.0],[1606.0,1178.0],[1197.0,1989.0],[378.0,1691.0]],[[919.0,818.0],[1972.0,497.0],[1470.0,341.0],[128.0,334.0]],[[1323.0,1000.0],[1812.0,371.0],[789.0,135.0],[1807.0,543.0]],[[350.0,1928.0],[264.0,204.0],[590.0,2024.0],[513.0,2024.0]],[[1786.0,1963.0],[1205.0,46.0],[1024.0,487.0],[373.0,1496.0]],[[137.0,955.0],[620.0,89.0],[1560.0,171.0],[542.0,1365.0]],[[1569.0,297.0],[1844.0,1261.0],[1342.0,847.0],[1748.0,1982.0]],[[262.0,1559.0],[1032.0,18.0],[431.0,1237.0],[901.0,1548.0]],[[53.0,1603.0],[309.0,368.0],[997.0,256.0],[1716.0,1830.0]],[[1274.0,133.0],[588.0,2023.0],[1987.0,1505.0],[614.0,477.0]],[[845.0,1096.0],[57.0,2035.0],[1854.0,589.0],[604.0,1295.0]],[[1639.0,1808.0],[1116.0,1878.0],[1516.0,1625.0],[343.0,89.0]],[[883.0,912.0],[1009.0,1423.0],[1774.0,393.0],[339.0,119.0]],[[956.0,1357.0],[983.0,822.0],[670.0,344.0],[661.0,251.0]],[[1230.0,1026.0],[1367.0,1425.0],[1735.0,1522.0],[843.0,6.0]],[[2044.0,536.0],[731.0,1561.0],[8.0,746.0],[68.0,1085.0]],[[1498.0,894.0],[1666.0,775.0],[997.0,1712.0],[831.0,1701.0]],[[1554.0,176.0],[700.0,5.0],[851.0,410.0],[235.0,1192.0]],[[603.0,1609.0],[1475.0,753.0],[690.0,669.0],[602.0,2047.0]],[[362.0,1832.0],[748.0,1179.0],[1094.0,1432.0],[1812.0,992.0]],[[178.0,937.0],[891.0,1101.0],[980.0,1762.0],[1146.0,963.0]],[[1473.0,2007.0],[1683.0,827.0],[1878.0,1292.0],[190.0,952.0]],[[911.0,635.0],[1734.0,504.0],[300.0,1706.0],[1744.0,1560.0]],[[1876.0,387.0],[829.0,241.0],[1600.0,854.0],[864.0,1770.0]],[[1931.0,1532.0],[430.0,1541.0],[1204.0,1505.0],[1281.0,720.0]],[[1727.0,1009.0],[779.0,307.0],[929.0,350.0],[1042.0,1720.0]],[[913.0,2016.0],[1048.0,137.0],[1613.0,1411.0],[1644.0,1901.0]],[[1707.0,836.0],[223.0,1169.0],[951.0,618.0],[585.0,1379.0]],[[1215.0,1848.0],[1743.0,1234.0],[1532.0,1282.0],[1062.0,931.0]],[[1857.0,1615.0],[1350.0,156.0],[862.0,390.0],[558.0,1012.0]],[[1440.0,589.0],[1830.0,1634.0],[1026.0,51.0],[1706.0,1293.0]],[[586.0,1304.0],[283.0,847.0],[1582.0,861.0],[1638.0,369.0]],[[1917.0,865.0],[461.0,567.0],[1556.0,457.0],[1581.0,1003.0]],[[1912.0,1978.0],[335.0,662.0],[23.0,1445.0],[230.0,1882.0]],[[559.0,2047.0],[1571.0,826.0],[773.0,251.0],[1393.0,805.0]],[[1960.0,1148.0],[731.0,2037.0],[1709.0,196.0],[635.0,787.0]],[[41.0,1259.0],[1715.0,292.0],[755.0,98.0],[1542.0,1185.0]],[[607.0,1168.0],[564.0,699.0],[1222.0,957.0],[1263.0,1837.0]],[[866.0,796.0],[213.0,843.0],[1541.0,1342.0],[1459.0,1569.0]],[[180.0,1619.0],[361.0,644.0],[2021.0,630.0],[1443.0,1266.0]],[[740.0,164.0],[1974.0,1888.0],[1459.0,1412.0],[1422.0,891.0]],[[547.0,988.0],[1697.0,258.0],[982.0,1053.0],[1226.0,1899.0]],[[1369.0,1813.0],[66.0,2014.0],[1998.0,543.0],[108.0,1741.0]],[[1586.0,766.0],[1170.0,1547.0],[780.0,1635.0],[105.0,996.0]],[[1983.0,1392.0],[1484.0,1823.0],[1267.0,322.0],[237.0,1291.0]],[[644.0,557.0],[216.0,1481.0],[1935.0,990.0],[174.0,142.0]],[[59.0,1955.0],[1878.0,1912.0],[1758.0,994.0],[1662.0,720.0]],[[424.0,994.0],[1166.0,1246.0],[942.0,102.0],[432.0,841.0]],[[1759.0,1169.0],[2030.0,1148.0],[1255.0,449.0],[1910.0,568.0]],[[1151.0,1281.0],[1275.0,1391.0],[145.0,1216.0],[805.0,1045.0]],[[1902.0,912.0],[13.0,1282.0],[1870.0,454.0],[264.0,1746.0]],[[1446.0,1033.0],[172.0,573.0],[1250.0,1534.0],[1406.0,1635.0]],[[733.0,214.0],[1917.0,1336.0],[669.0,939.0],[1966.0,667.0]],[[1747.0,1630.0],[448.0,1279.0],[374.0,1880.0],[665.0,1782.0]],[[2009.0,574.0],[258.0,127.0],[1963.0,542.0],[978.0,182.0]],[[1235.0,1695.0],[1897.0,1053.0],[1428.0,1364.0],[1496.0,138.0]],[[737.0,1749.0],[911.0,2034.0],[1521.0,1036.0],[995.0,270.0]],[[1063.0,1198.0],[138.0,1202.0],[1958.0,1973.0],[670.0,378.0]],[[815.0,205.0],[1390.0,1171.0],[270.0,925.0],[694.0,595.0]],[[1774.0,1285.0],[818.0,1904.0],[758.0,3.0],[828.0,207.0]],[[71.0,313.0],[1584.0,1480.0],[1790.0,768.0],[660.0,20.0]],[[1744.0,129.0],[1007.0,254.0],[1609.0,461.0],[1513.0,1533.0]],[[547.0,1559.0],[384.0,1303.0],[2006.0,15.0],[1915.0,962.0]],[[1653.0,327.0],[1773.0,1540.0],[78.0,1409.0],[1201.0,949.0]],[[1631.0,2033.0],[1655.0,1512.0],[204.0,252.0],[1669.0,1754.0]],[[643.0,594.0],[1354.0,780.0],[577.0,2014.0],[1692.0,1284.0]],[[24.0,1497.0],[1526.0,1484.0],[328.0,1571.0],[539.0,148.0]],[[579.0,202.0],[1264.0,1083.0],[1363.0,166.0],[324.0,522.0]],[[562.0,982.0],[1300.0,863.0],[644.0,1818.0],[815.0,1673.0]],[[1525.0,539.0],[1274.0,751.0],[1884.0,101.0],[730.0,499.0]],[[129.0,577.0],[1696.0,1195.0],[870.0,233.0],[290.0,1737.0]],[[1099.0,1925.0],[1806.0,1906.0],[333.0,1405.0],[435.0,1228.0]],[[938.0,182.0],[973.0,475.0],[842.0,733.0],[1609.0,1659.0]],[[527.0,946.0],[846.0,1998.0],[628.0,477.0],[1569.0,1822.0]],[[1470.0,85.0],[1971.0,1998.0],[240.0,1020.0],[1162.0,710.0]],[[1920.0,279.0],[1302.0,354.0],[1069.0,1236.0],[531.0,243.0]],[[1291.0,324.0],[603.0,2048.0],[533.0,707.0],[100.0,2.0]],[[251.0,283.0],[1478.0,1954.0],[196.0,317.0],[1882.0,1861.0]],[[72.0,1128.0],[1457.0,1837.0],[329.0,1421.0],[263.0,716.0]],[[725.0,17.0],[1616.0,793.0],[917.0,633.0],[1992.0,502.0]],[[1424.0,2010.0],[421.0,724.0],[2048.0,1265.0],[398.0,1228.0]],[[361.0,477.0],[1319.0,1807.0],[1181.0,1801.0],[1182.0,1798.0]],[[1890.0,612.0],[1244.0,1104.0],[317.0,1171.0],[138.0,1623.0]],[[532.0,2005.0],[1324.0,1074.0],[497.0,1185.0],[1784.0,908.0]],[[999.0,1599.0],[298.0,1647.0],[517.0,1039.0],[172.0,1038.0]],[[963.0,1388.0],[330.0,1058.0],[1562.0,427.0],[538.0,990.0]],[[1991.0,1327.0],[1893.0,1666.0],[332.0,1918.0],[147.0,1779.0]],[[813.0,1614.0],[1066.0,524.0],[545.0,1546.0],[226.0,73.0]],[[219.0,1805.0],[15.0,1417.0],[1824.0,1337.0],[760.0,856.0]],[[1805.0,877.0],[819.0,1594.0],[1359.0,1813.0],[1142.0,1123.0]],[[661.0,1229.0],[357.0,288.0],[1964.0,998.0],[391.0,2046.0]],[[491.0,1842.0],[683.0,462.0],[1330.0,1545.0],[918.0,392.0]],[[1846.0,936.0],[458.0,357.0],[1950.0,1031.0],[1238.0,81.0]],[[1366.0,1574.0],[999.0,1766.0],[625.0,1838.0],[278.0,1948.0]],[[1050.0,1677.0],[278.0,500.0],[1573.0,485.0],[1095.0,1549.0]],[[131.0,376.0],[1409.0,305.0],[1605.0,1924.0],[1963.0,643.0]],[[1638.0,173.0],[1070.0,1631.0],[1605.0,1578.0],[1853.0,63.0]],[[994.0,535.0],[300.0,2043.0],[1655.0,858.0],[439.0,1891.0]],[[804.0,1655.0],[1663.0,1780.0],[105.0,1149.0],[89.0,178.0]],[[1992.0,1713.0],[535.0,721.0],[774.0,1541.0],[1162.0,324.0]],[[173.0,1692.0],[598.0,1912.0],[651.0,1216.0],[162.0,1231.0]],[[1198.0,128.0],[1341.0,1203.0],[130.0,1997.0],[355.0,669.0]],[[1569.0,2019.0],[1080.0,1839.0],[92.0,1720.0],[143.0,1208.0]],[[201.0,1421.0],[817.0,1402.0],[740.0,1002.0],[566.0,372.0]],[[166.0,386.0],[632.0,1258.0],[1294.0,2019.0],[1941.0,87.0]],[[1577.0,1553.0],[960.0,582.0],[428.0,1708.0],[1580.0,916.0]],[[34.0,1295.0],[1679.0,1591.0],[894.0,543.0],[1790.0,723.0]],[[448.0,461.0],[1220.0,719.0],[1440.0,1935.0],[2038.0,657.0]],[[1094.0,982.0],[1081.0,288.0],[1332.0,1002.0],[899.0,1076.0]],[[661.0,536.0],[232.0,1895.0],[1460.0,528.0],[1017.0,1247.0]],[[169.0,1738.0],[1829.0,2018.0],[217.0,710.0],[1549.0,2041.0]],[[1280.0,1256.0],[1139.0,648.0],[1465.0,1509.0],[379.0,1758.0]],[[992.0,1488.0],[386.0,1443.0],[994.0,270.0],[1501.0,1272.0]],[[176.0,1450.0],[51.0,1577.0],[393.0,9.0],[9.0,733.0]],[[1892.0,446.0],[886.0,1528.0],[272.0,565.0],[1641.0,754.0]],[[1793.0,388.0],[1115.0,1962.0],[746.0,1533.0],[618.0,495.0]],[[546.0,318.0],[1622.0,660.0],[747.0,919.0],[1176.0,438.0]],[[201.0,891.0],[25.0,881.0],[51.0,1811.0],[785.0,1540.0]],[[1216.0,611.0],[1389.0,1523.0],[1551.0,443.0],[795.0,1389.0]],[[33.0,1730.0],[1828.0,791.0],[677.0,90.0],[1078.0,1918.0]],[[953.0,337.0],[266.0,880.0],[574.0,1018.0],[981.0,1478.0]],[[76.0,810.0],[1842.0,131.0],[404.0,713.0],[1683.0,526.0]],[[1989.0,940.0],[982.0,230.0],[1521.0,692.0],[668.0,1767.0]],[[1007.0,1101.0],[1418.0,831.0],[1209.0,969.0],[419.0,168.0]],[[1350.0,1252.0],[731.0,515.0],[1692.0,1301.0],[1164.0,95.0]],[[1014.0,117.0],[1302.0,1954.0],[207.0,838.0],[1635.0,405.0]],[[1802.0,1658.0],[2023.0,829.0],[1833.0,438.0],[788.0,718.0]],[[1055.0,1075.0],[30.0,275.0],[1582.0,75.0],[783.0,1363.0]],[[1122.0,840.0],[1957.0,606.0],[1852.0,169.0],[1557.0,1617.0]],[[1646.0,226.0],[247.0,1184.0],[1448.0,959.0],[804.0,1882.0]],[[1925.0,1922.0],[154.0,782.0],[482.0,1316.0],[1226.0,1753.0]],[[1019.0,643.0],[1135.0,1091.0],[1534.0,438.0],[1472.0,1740.0]],[[1736.0,1848.0],[1962.0,915.0],[402.0,724.0],[1848.0,1913.0]],[[233.0,419.0],[345.0,1642.0],[1633.0,1089.0],[783.0,2015.0]],[[1464.0,1539.0],[462.0,50.0],[972.0,2001.0],[1578.0,1077.0]],[[1092.0,349.0],[664.0,1613.0],[401.0,210.0],[868.0,1412.0]],[[737.0,712.0],[1190.0,472.0],[959.0,60.0],[1713.0,1180.0]],[[642.0,597.0],[546.0,1342.0],[779.0,503.0],[1369.0,2021.0]],[[827.0,1279.0],[160.0,781.0],[722.0,1352.0],[780.0,787.0]],[[1735.0,453.0],[1336.0,1103.0],[731.0,10.0],[1242.0,1567.0]],[[1580.0,1655.0],[260.0,1298.0],[1376.0,552.0],[1061.0,1096.0]],[[1807.0,654.0],[1888.0,784.0],[1571.0,1922.0],[382.0,1811.0]],[[498.0,765.0],[1052.0,1267.0],[1680.0,1205.0],[1223.0,816.0]],[[1462.0,533.0],[880.0,426.0],[1765.0,887.0],[1073.0,969.0]],[[540.0,278.0],[1381.0,213.0],[1826.0,1026.0],[239.0,235.0]],[[1006.0,150.0],[1450.0,380.0],[692.0,739.0],[784.0,768.0]],[[177.0,47.0],[1673.0,2023.0],[245.0,982.0],[984.0,442.0]],[[1331.0,1746.0],[723.0,131.0],[1582.0,1248.0],[1449.0,126.0]],[[1882.0,1398.0],[1581.0,1793.0],[2043.0,1939.0],[1455.0,670.0]],[[1790.0,249.0],[1346.0,1061.0],[196.0,127.0],[1573.0,630.0]],[[1945.0,804.0],[1597.0,1914.0],[731.0,927.0],[244.0,673.0]],[[1684.0,1856.0],[4.0,811.0],[190.0,1624.0],[301.0,1647.0]],[[23.0,579.0],[921.0,1636.0],[1389.0,852.0],[419.0,1593.0]],[[1297.0,134.0],[313.0,827.0],[1434.0,1441.0],[174.0,1750.0]],[[1252.0,1783.0],[1201.0,658.0],[691.0,549.0],[51.0,2.0]],[[1784.0,881.0],[909.0,1798.0],[393.0,30.0],[1787.0,652.0]],[[1179.0,1892.0],[761.0,1600.0],[786.0,1188.0],[1297.0,470.0]],[[484.0,485.0],[1981.0,210.0],[1573.0,1456.0],[226.0,432.0]],[[1900.0,1094.0],[238.0,1342.0],[1792.0,512.0],[1272.0,1876.0]],[[195.0,519.0],[1180.0,464.0],[500.0,333.0],[1754.0,1763.0]],[[351.0,1732.0],[1457.0,689.0],[2017.0,2045.0],[1698.0,1202.0]],[[1892.0,1913.0],[991.0,855.0],[788.0,540.0],[254.0,1134.0]],[[56.0,1809.0],[1837.0,718.0],[853.0,1184.0],[1239.0,595.0]],[[348.0,755.0],[369.0,682.0],[698.0,1668.0],[689.0,873.0]],[[1727.0,1035.0],[1389.0,628.0],[950.0,1698.0],[1344.0,46.0]],[[945.0,699.0],[1197.0,232.0],[458.0,924.0],[124.0,1746.0]],[[18.0,138.0],[713.0,77.0],[1800.0,1734.0],[600.0,1491.0]],[[1642.0,923.0],[2028.0,1144.0],[499.0,1180.0],[96.0,1201.0]],[[253.0,1172.0],[225.0,377.0],[1516.0,1458.0],[469.0,1844.0]],[[1124.0,140.0],[227.0,1118.0],[1387.0,1428.0],[1004.0,1149.0]],[[2007.0,1082.0],[276.0,939.0],[1672.0,1559.0],[1924.0,664.0]],[[1691.0,1877.0],[1779.0,907.0],[1870.0,335.0],[1616.0,1556.0]],[[772.0,822.0],[545.0,153.0],[585.0,1871.0],[1438.0,1443.0]],[[384.0,1038.0],[987.0,1309.0],[1268.0,72.0],[1045.0,1780.0]],[[1863.0,1087.0],[1395.0,340.0],[50.0,1100.0],[1743.0,633.0]],[[946.0,1521.0],[1577.0,20.0],[967.0,717.0],[909.0,490.0]],[[786.0,385.0],[790.0,945.0],[173.0,1695.0],[1400.0,590.0]],[[51.0,1909.0],[886.0,248.0],[140.0,409.0],[147.0,768.0]],[[1447.0,257.0],[1542.0,888.0],[1304.0,1602.0],[1192.0,702.0]],[[1196.0,1264.0],[1143.0,1484.0],[935.0,901.0],[489.0,1547.0]],[[582.0,1474.0],[1163.0,1440.0],[1459.0,215.0],[232.0,370.0]],[[1778.0,1388.0],[1460.0,986.0],[1249.0,1169.0],[1582.0,1321.0]],[[683.0,609.0],[410.0,621.0],[911.0,1239.0],[1780.0,1728.0]],[[1079.0,1498.0],[1265.0,1812.0],[1832.0,231.0],[1863.0,1680.0]],[[1.0,506.0],[1220.0,742.0],[298.0,461.0],[979.0,1486.0]],[[1835.0,1501.0],[1185.0,615.0],[1136.0,419.0],[528.0,2002.0]],[[1357.0,950.0],[1443.0,154.0],[228.0,481.0],[752.0,2035.0]],[[1863.0,382.0],[1663.0,250.0],[614.0,296.0],[1244.0,45.0]],[[1879.0,993.0],[945.0,884.0],[534.0,1662.0],[541.0,610.0]],[[641.0,1449.0],[229.0,1523.0],[120.0,2032.0],[1851.0,377.0]],[[143.0,1835.0],[562.0,1566.0],[1088.0,247.0],[155.0,155.0]],[[754.0,910.0],[1155.0,486.0],[1248.0,756.0],[146.0,438.0]],[[1959.0,608.0],[1041.0,472.0],[344.0,1347.0],[543.0,944.0]],[[931.0,1377.0],[1803.0,1427.0],[46.0,269.0],[1118.0,757.0]],[[59.0,1514.0],[1205.0,697.0],[46.0,2009.0],[686.0,1485.0]],[[1496.0,1758.0],[2046.0,1933.0],[1411.0,1672.0],[400.0,309.0]],[[404.0,937.0],[1330.0,845.0],[154.0,244.0],[1442.0,906.0]],[[1708.0,1543.0],[211.0,209.0],[1771.0,1137.0],[1535.0,299.0]],[[423.0,1345.0],[735.0,1425.0],[1590.0,1912.0],[650.0,717.0]],[[1717.0,1933.0],[774.0,108.0],[652.0,811.0],[751.0,331.0]],[[107.0,366.0],[781.0,1854.0],[1054.0,249.0],[1632.0,1104.0]],[[1727.0,970.0],[1851.0,508.0],[284.0,868.0],[851.0,891.0]],[[125.0,717.0],[1686.0,2002.0],[1800.0,1356.0],[728.0,784.0]],[[1722.0,380.0],[1561.0,1490.0],[1482.0,353.0],[1883.0,30.0]],[[802.0,1142.0],[1461.0,1471.0],[114.0,768.0],[1007.0,1741.0]],[[1329.0,1511.0],[1804.0,1895.0],[452.0,145.0],[857.0,1402.0]],[[316.0,1766.0],[1034.0,997.0],[237.0,1274.0],[385.0,1274.0]],[[604.0,1762.0],[1479.0,340.0],[1254.0,295.0],[1812.0,1958.0]],[[671.0,980.0],[4.0,121.0],[813.0,863.0],[1343.0,375.0]],[[1596.0,1680.0],[1549.0,1401.0],[1551.0,1793.0],[1153.0,1460.0]],[[779.0,573.0],[1220.0,388.0],[1996.0,1434.0],[1624.0,756.0]],[[135.0,347.0],[2016.0,245.0],[1608.0,633.0],[561.0,751.0]],[[282.0,1611.0],[791.0,1606.0],[1454.0,1696.0],[713.0,1666.0]],[[1124.0,956.0],[1535.0,33.0],[1199.0,1178.0],[1096.0,597.0]],[[843.0,1907.0],[159.0,1109.0],[470.0,359.0],[210.0,272.0]],[[2039.0,554.0],[1993.0,1892.0],[1595.0,925.0],[217.0,1700.0]],[[694.0,215.0],[1190.0,1860.0],[183.0,636.0],[781.0,852.0]],[[660.0,875.0],[517.0,77.0],[1539.0,70.0],[1659.0,1506.0]],[[1477.0,1366.0],[619.0,1319.0],[1321.0,1390.0],[240.0,1326.0]],[[1856.0,664.0],[380.0,61.0],[109.0,1996.0],[424.0,771.0]],[[1596.0,1686.0],[449.0,1074.0],[1787.0,179.0],[1880.0,886.0]],[[178.0,11.0],[1201.0,1722.0],[260.0,1371.0],[1414.0,1644.0]],[[252.0,1102.0],[539.0,838.0],[53.0,657.0],[1828.0,217.0]],[[1685.0,447.0],[133.0,689.0],[1887.0,266.0],[47.0,35.0]],[[577.0,924.0],[11.0,128.0],[962.0,1783.0],[441.0,1995.0]],[[1527.0,1367.0],[671.0,871.0],[1000.0,343.0],[891.0,676.0]],[[1233.0,379.0],[322.0,853.0],[529.0,308.0],[1842.0,1056.0]],[[1479.0,1012.0],[577.0,705.0],[979.0,1739.0],[305.0,1523.0]],[[711.0,246.0],[177.0,1143.0],[26.0,1955.0],[1704.0,786.0]],[[460.0,1444.0],[1372.0,1058.0],[674.0,1165.0],[1215.0,1996.0]],[[1327.0,1752.0],[455.0,196.0],[266.0,260.0],[1277.0,1724.0]],[[1582.0,1524.0],[1344.0,367.0],[601.0,1804.0],[1710.0,921.0]],[[1036.0,1697.0],[1377.0,1277.0],[708.0,171.0],[462.0,411.0]],[[1793.0,498.0],[1947.0,1429.0],[1128.0,489.0],[1287.0,1564.0]],[[498.0,673.0],[1836.0,363.0],[2040.0,1402.0],[508.0,1317.0]],[[400.0,404.0],[998.0,2028.0],[993.0,1990.0],[1380.0,181.0]],[[682.0,252.0],[63.0,1755.0],[1358.0,1248.0],[1007.0,1455.0]],[[598.0,289.0],[771.0,142.0],[721.0,1464.0],[1785.0,1079.0]],[[860.0,659.0],[858.0,1660.0],[758.0,716.0],[979.0,1903.0]],[[586.0,1154.0],[1327.0,659.0],[1308.0,1154.0],[986.0,799.0]],[[683.0,1154.0],[131.0,582.0],[533.0,105.0],[1787.0,213.0]],[[1434.0,1838.0],[593.0,1752.0],[884.0,642.0],[1467.0,713.0]],[[280.0,1073.0],[1521.0,1374.0],[198.0,1249.0],[637.0,1968.0]],[[212.0,1906.0],[1708.0,359.0],[74.0,33.0],[177.0,66.0]],[[118.0,881.0],[1512.0,834.0],[1845.0,181.0],[203.0,907.0]],[[1221.0,821.0],[732.0,790.0],[813.0,1521.0],[880.0,554.0]],[[409.0,1286.0],[656.0,1903.0],[2044.0,94.0],[1959.0,277.0]],[[53.0,1860.0],[1309.0,1470.0],[1416.0,1685.0],[586.0,508.0]],[[446.0,1895.0],[1332.0,681.0],[287.0,1577.0],[85.0,1818.0]],[[0.0,552.0],[1214.0,1151.0],[1244.0,244.0],[1949.0,1945.0]],[[251.0,611.0],[3.0,2036.0],[1686.0,151.0],[997.0,477.0]],[[1591.0,114.0],[1260.0,27.0],[92.0,987.0],[1577.0,1140.0]],[[1490.0,22.0],[873.0,1756.0],[874.0,1853.0],[202.0,529.0]],[[1107.0,139.0],[397.0,1623.0],[391.0,197.0],[2026.0,2006.0]],[[612.0,456.0],[2001.0,1026.0],[746.0,246.0],[443.0,1902.0]],[[293.0,750.0],[1381.0,1908.0],[643.0,1997.0],[1533.0,1053.0]],[[1218.0,1982.0],[705.0,1236.0],[119.0,722.0],[996.0,428.0]],[[1593.0,315.0],[496.0,1209.0],[404.0,1039.0],[904.0,710.0]],[[1480.0,438.0],[1841.0,407.0],[1322.0,271.0],[1828.0,1779.0]],[[357.0,458.0],[1916.0,1935.0],[1050.0,1561.0],[1905.0,1937.0]],[[1552.0,1571.0],[399.0,1540.0],[1367.0,42.0],[283.0,711.0]],[[2016.0,1763.0],[122.0,350.0],[1590.0,1352.0],[162.0,110.0]],[[3.0,1847.0],[717.0,1853.0],[1239.0,1664.0],[316.0,837.0]],[[1749.0,1303.0],[2015.0,1759.0],[1603.0,126.0],[418.0,700.0]],[[1659.0,634.0],[1043.0,881.0],[2026.0,563.0],[1407.0,422.0]],[[647.0,2018.0],[79.0,1274.0],[154.0,399.0],[1102.0,2018.0]],[[643.0,1683.0],[1029.0,1441.0],[1206.0,1326.0],[1037.0,760.0]],[[876.0,445.0],[640.0,1032.0],[495.0,828.0],[181.0,562.0]],[[1857.0,471.0],[1012.0,727.0],[1710.0,1727.0],[679.0,163.0]],[[1344.0,51.0],[1313.0,697.0],[604.0,1509.0],[1011.0,172.0]],[[1715.0,640.0],[1337.0,877.0],[997.0,1970.0],[1153.0,1105.0]],[[1975.0,916.0],[663.0,269.0],[1021.0,1862.0],[568.0,1540.0]],[[688.0,84.0],[385.0,233.0],[139.0,225.0],[2040.0,1901.0]],[[99.0,2016.0],[1074.0,1160.0],[1129.0,1561.0],[1960.0,487.0]],[[359.0,1272.0],[1545.0,1783.0],[806.0,963.0],[629.0,282.0]],[[1184.0,845.0],[414.0,1432.0],[1102.0,560.0],[905.0,1466.0]],[[1439.0,652.0],[424.0,429.0],[805.0,1243.0],[964.0,268.0]],[[168.0,1361.0],[1545.0,874.0],[1495.0,314.0],[1880.0,1915.0]],[[1359.0,1198.0],[1361.0,78.0],[352.0,527.0],[677.0,82.0]],[[108.0,1015.0],[836.0,1769.0],[1268.0,758.0],[437.0,1538.0]],[[36.0,807.0],[273.0,894.0],[654.0,1282.0],[834.0,1297.0]],[[938.0,1073.0],[1422.0,1605.0],[1300.0,200.0],[1864.0,819.0]],[[449.0,1889.0],[636.0,1596.0],[1544.0,849.0],[1285.0,1389.0]],[[975.0,2030.0],[1912.0,1633.0],[17.0,1908.0],[1440.0,1971.0]],[[1381.0,224.0],[1813.0,1607.0],[1948.0,1998.0],[1178.0,246.0]],[[1894.0,949.0],[383.0,1239.0],[667.0,1847.0],[864.0,1095.0]],[[666.0,1239.0],[1606.0,27.0],[771.0,1966.0],[56.0,1199.0]],[[1087.0,1063.0],[315.0,1598.0],[190.0,157.0],[803.0,1937.0]],[[885.0,1616.0],[1325.0,1224.0],[1994.0,1049.0],[1079.0,658.0]],[[1622.0,1655.0],[2047.0,924.0],[964.0,1665.0],[1830.0,307.0]],[[1586.0,230.0],[5.0,777.0],[1442.0,807.0],[432.0,154.0]],[[1932.0,782.0],[222.0,731.0],[1293.0,175.0],[1046.0,844.0]],[[782.0,1670.0],[265.0,735.0],[1047.0,184.0],[595.0,56.0]],[[242.0,883.0],[717.0,1342.0],[573.0,706.0],[1654.0,4.0]],[[11.0,1364.0],[109.0,881.0],[326.0,1974.0],[1900.0,303.0]],[[699.0,1105.0],[75.0,1695.0],[1193.0,820.0],[742.0,1309.0]],[[402.0,1192.0],[1242.0,820.0],[456.0,50.0],[1148.0,850.0]],[[886.0,963.0],[813.0,2041.0],[1300.0,1634.0],[1974.0,428.0]],[[1521.0,307.0],[1907.0,1033.0],[1948.0,244.0],[82.0,1361.0]],[[1959.0,43.0],[1009.0,514.0],[201.0,1616.0],[393.0,948.0]],[[216.0,1228.0],[1515.0,420.0],[394.0,1129.0],[1149.0,627.0]],[[964.0,639.0],[1955.0,228.0],[1686.0,1802.0],[123.0,50.0]],[[1356.0,1779.0],[1221.0,1998.0],[211.0,1807.0],[1303.0,1740.0]],[[104.0,690.0],[1150.0,1485.0],[985.0,277.0],[593.0,1308.0]],[[1618.0,1267.0],[1320.0,1503.0],[1998.0,577.0],[858.0,1704.0]],[[881.0,424.0],[332.0,703.0],[60.0,87.0],[339.0,1860.0]],[[300.0,760.0],[1470.0,709.0],[751.0,1706.0],[310.0,1892.0]],[[444.0,10.0],[1973.0,1494.0],[1653.0,589.0],[1490.0,569.0]],[[969.0,1595.0],[1337.0,773.0],[408.0,1264.0],[1803.0,846.0]],[[1859.0,622.0],[1049.0,219.0],[669.0,804.0],[2011.0,807.0]],[[174.0,1636.0],[1411.0,1956.0],[1701.0,906.0],[796.0,329.0]],[[63.0,637.0],[2027.0,883.0],[2035.0,1101.0],[1612.0,506.0]],[[286.0,401.0],[1420.0,690.0],[814.0,1751.0],[1268.0,1676.0]],[[1055.0,1004.0],[1544.0,1670.0],[139.0,163.0],[788.0,1524.0]],[[1661.0,1318.0],[1678.0,1132.0],[1851.0,1604.0],[719.0,527.0]],[[1762.0,1430.0],[1498.0,1528.0],[1781.0,140.0],[1507.0,506.0]],[[1368.0,219.0],[871.0,1467.0],[732.0,1173.0],[2044.0,712.0]],[[1732.0,1509.0],[1520.0,124.0],[682.0,749.0],[1470.0,1925.0]],[[1827.0,786.0],[501.0,1314.0],[67.0,1755.0],[911.0,1798.0]],[[1264.0,1721.0],[1129.0,325.0],[813.0,1854.0],[457.0,156.0]],[[19.0,918.0],[981.0,130.0],[2041.0,1358.0],[838.0,780.0]],[[455.0,116.0],[1226.0,1149.0],[387.0,546.0],[1518.0,1843.0]],[[1735.0,1348.0],[254.0,1244.0],[343.0,1899.0],[911.0,652.0]],[[859.0,1198.0],[1924.0,1174.0],[1148.0,405.0],[1039.0,660.0]],[[727.0,681.0],[1757.0,1236.0],[1626.0,1630.0],[949.0,686.0]],[[1687.0,702.0],[1352.0,999.0],[891.0,609.0],[1178.0,306.0]],[[308.0,980.0],[472.0,1248.0],[43.0,540.0],[1662.0,1620.0]],[[230.0,510.0],[215.0,539.0],[1906.0,1617.0],[1196.0,976.0]],[[651.0,1927.0],[958.0,922.0],[1029.0,1711.0],[1886.0,1152.0]],[[541.0,528.0],[132.0,535.0],[630.0,38.0],[1035.0,712.0]],[[140.0,1420.0],[1969.0,763.0],[949.0,1332.0],[755.0,864.0]],[[706.0,2019.0],[1013.0,513.0],[855.0,191.0],[1370.0,1010.0]],[[1656.0,1872.0],[1085.0,746.0],[479.0,180.0],[356.0,148.0]],[[1172.0,2040.0],[206.0,492.0],[1278.0,1519.0],[1416.0,979.0]],[[867.0,1050.0],[778.0,664.0],[1391.0,1142.0],[1323.0,1484.0]],[[151.0,319.0],[873.0,710.0],[1613.0,1875.0],[859.0,50.0]],[[1897.0,1624.0],[1548.0,1427.0],[1519.0,2017.0],[34.0,1033.0]],[[316.0,238.0],[1656.0,1666.0],[1530.0,1642.0],[757.0,1247.0]],[[800.0,367.0],[1244.0,121.0],[1739.0,158.0],[1875.0,23.0]],[[1653.0,831.0],[1949.0,1049.0],[97.0,857.0],[914.0,569.0]],[[1357.0,980.0],[1078.0,1356.0],[146.0,1577.0],[1187.0,71.0]],[[1916.0,105.0],[1741.0,64.0],[447.0,709.0],[1016.0,76.0]],[[1657.0,1822.0],[629.0,315.0],[879.0,782.0],[1707.0,1466.0]],[[1504.0,256.0],[902.0,804.0],[385.0,522.0],[1133.0,1420.0]],[[1341.0,1738.0],[1080.0,1689.0],[1455.0,933.0],[306.0,1861.0]],[[896.0,1132.0],[42.0,1304.0],[774.0,1471.0],[1246.0,1532.0]],[[807.0,1437.0],[1321.0,1097.0],[1666.0,367.0],[1544.0,780.0]],[[1838.0,806.0],[886.0,1333.0],[1962.0,1799.0],[20.0,197.0]],[[1227.0,517.0],[1594.0,290.0],[1202.0,525.0],[1321.0,1319.0]],[[1581.0,1948.0],[598.0,1052.0],[900.0,1009.0],[1544.0,1509.0]],[[204.0,1234.0],[1896.0,1409.0],[21.0,611.0],[1950.0,2040.0]],[[78.0,133.0],[1601.0,1784.0],[440.0,839.0],[1740.0,1523.0]],[[1764.0,1845.0],[1574.0,1740.0],[257.0,2044.0],[747.0,876.0]],[[761.0,408.0],[1739.0,1479.0],[900.0,736.0],[1577.0,1455.0]],[[1515.0,578.0],[652.0,956.0],[1051.0,1013.0],[292.0,324.0]],[[829.0,1789.0],[2015.0,156.0],[1316.0,901.0],[307.0,1759.0]],[[345.0,1307.0],[746.0,1956.0],[965.0,1449.0],[815.0,1160.0]],[[685.0,1855.0],[460.0,115.0],[418.0,1039.0],[1250.0,423.0]],[[1163.0,829.0],[1895.0,920.0],[116.0,1701.0],[303.0,1392.0]],[[1906.0,1850.0],[1850.0,915.0],[1771.0,1131.0],[1554.0,356.0]],[[1635.0,1624.0],[409.0,427.0],[1493.0,1446.0],[1438.0,1622.0]],[[1952.0,1994.0],[843.0,882.0],[577.0,847.0],[474.0,56.0]],[[754.0,1994.0],[1407.0,1031.0],[1273.0,366.0],[860.0,577.0]],[[1035.0,976.0],[392.0,1224.0],[1705.0,1318.0],[1460.0,635.0]],[[1880.0,932.0],[1281.0,849.0],[1346.0,525.0],[563.0,1618.0]],[[1154.0,151.0],[120.0,140.0],[1666.0,1503.0],[174.0,1889.0]],[[1866.0,1212.0],[1635.0,1655.0],[1353.0,1037.0],[1994.0,327.0]],[[82.0,612.0],[1294.0,264.0],[1474.0,223.0],[248.0,1570.0]],[[1318.0,884.0],[568.0,1755.0],[957.0,1160.0],[1395.0,19.0]],[[921.0,871.0],[1085.0,1656.0],[1231.0,1927.0],[1660.0,1997.0]],[[1909.0,1080.0],[69.0,1910.0],[1836.0,1863.0],[1744.0,892.0]],[[1499.0,1791.0],[696.0,714.0],[1872.0,1449.0],[94.0,881.0]],[[454.0,1530.0],[164.0,1837.0],[266.0,1332.0],[397.0,372.0]],[[1496.0,936.0],[371.0,1362.0],[528.0,996.0],[1439.0,960.0]],[[1095.0,1004.0],[1600.0,149.0],[59.0,1065.0],[1147.0,2041.0]],[[476.0,1992.0],[1687.0,1146.0],[396.0,252.0],[1408.0,1998.0]],[[1504.0,1082.0],[821.0,1755.0],[1567.0,695.0],[1510.0,286.0]],[[1823.0,1154.0],[1924.0,1508.0],[827.0,1351.0],[1575.0,1511.0]],[[697.0,1823.0],[1483.0,1026.0],[523.0,906.0],[653.0,1361.0]],[[9.0,508.0],[568.0,823.0],[538.0,204.0],[1564.0,575.0]],[[530.0,1391.0],[1935.0,1903.0],[393.0,64.0],[1592.0,599.0]],[[1552.0,353.0],[546.0,349.0],[1065.0,1214.0],[619.0,1367.0]],[[411.0,1814.0],[350.0,50.0],[791.0,1473.0],[1799.0,468.0]],[[773.0,70.0],[100.0,427.0],[1248.0,87.0],[1707.0,706.0]],[[1071.0,720.0],[191.0,94.0],[774.0,770.0],[708.0,495.0]],[[1472.0,197.0],[1264.0,452.0],[685.0,886.0],[0.0,206.0]],[[956.0,702.0],[1727.0,958.0],[578.0,20.0],[1913.0,2014.0]],[[129.0,604.0],[2041.0,1044.0],[1902.0,1457.0],[1385.0,1897.0]],[[1705.0,967.0],[1528.0,1908.0],[1963.0,2024.0],[26.0,768.0]],[[1361.0,784.0],[409.0,1371.0],[1909.0,184.0],[764.0,1070.0]],[[995.0,1834.0],[1771.0,524.0],[704.0,162.0],[1281.0,1979.0]],[[1220.0,1043.0],[1136.0,1543.0],[1002.0,917.0],[1406.0,1263.0]],[[1132.0,1305.0],[1559.0,1070.0],[995.0,340.0],[2031.0,678.0]],[[1526.0,652.0],[334.0,1809.0],[1953.0,1491.0],[1301.0,306.0]],[[885.0,909.0],[599.0,1454.0],[1404.0,702.0],[679.0,784.0]],[[837.0,845.0],[129.0,1698.0],[770.0,1882.0],[1680.0,1355.0]],[[118.0,1293.0],[733.0,516.0],[947.0,1713.0],[1994.0,709.0]],[[484.0,510.0],[401.0,65.0],[1908.0,730.0],[342.0,361.0]],[[572.0,52.0],[1671.0,1138.0],[780.0,1414.0],[1796.0,2002.0]],[[1898.0,2022.0],[1915.0,1071.0],[418.0,1378.0],[1394.0,260.0]],[[1870.0,1588.0],[1148.0,1615.0],[1358.0,278.0],[1238.0,560.0]],[[1063.0,81.0],[601.0,738.0],[767.0,1566.0],[511.0,1013.0]],[[1653.0,699.0],[1195.0,1417.0],[525.0,1630.0],[471.0,1557.0]],[[1484.0,935.0],[53.0,417.0],[870.0,1562.0],[320.0,119.0]],[[1503.0,1451.0],[1582.0,476.0],[567.0,678.0],[888.0,859.0]],[[1907.0,901.0],[742.0,1889.0],[1054.0,1982.0],[920.0,1603.0]],[[77.0,50.0],[507.0,711.0],[206.0,958.0],[1304.0,1292.0]],[[437.0,1710.0],[1935.0,568.0],[162.0,1116.0],[1463.0,859.0]],[[1404.0,2031.0],[560.0,1158.0],[1190.0,91.0],[1488.0,2027.0]]] \ No newline at end of file
diff --git a/Tests/cu2qu/ufo_test.py b/Tests/cu2qu/ufo_test.py
new file mode 100644
index 00000000..b678ae3d
--- /dev/null
+++ b/Tests/cu2qu/ufo_test.py
@@ -0,0 +1,285 @@
+import os
+
+from fontTools.misc.loggingTools import CapturingLogHandler
+from fontTools.cu2qu.ufo import (
+ fonts_to_quadratic,
+ font_to_quadratic,
+ glyphs_to_quadratic,
+ glyph_to_quadratic,
+ logger,
+ CURVE_TYPE_LIB_KEY,
+)
+from fontTools.cu2qu.errors import (
+ IncompatibleSegmentNumberError,
+ IncompatibleSegmentTypesError,
+ IncompatibleFontsError,
+)
+
+import pytest
+
+
+ufoLib2 = pytest.importorskip("ufoLib2")
+
+DATADIR = os.path.join(os.path.dirname(__file__), 'data')
+
+TEST_UFOS = [
+ os.path.join(DATADIR, "RobotoSubset-Regular.ufo"),
+ os.path.join(DATADIR, "RobotoSubset-Bold.ufo"),
+]
+
+
+@pytest.fixture
+def fonts():
+ return [ufoLib2.Font.open(ufo) for ufo in TEST_UFOS]
+
+
+class FontsToQuadraticTest(object):
+
+ def test_modified(self, fonts):
+ modified = fonts_to_quadratic(fonts)
+ assert modified
+
+ def test_stats(self, fonts):
+ stats = {}
+ fonts_to_quadratic(fonts, stats=stats)
+ assert stats == {'1': 1, '2': 79, '3': 130, '4': 2}
+
+ def test_dump_stats(self, fonts):
+ with CapturingLogHandler(logger, "INFO") as captor:
+ fonts_to_quadratic(fonts, dump_stats=True)
+ assert captor.assertRegex("New spline lengths:")
+
+ def test_remember_curve_type(self, fonts):
+ fonts_to_quadratic(fonts, remember_curve_type=True)
+ assert fonts[0].lib[CURVE_TYPE_LIB_KEY] == "quadratic"
+ with CapturingLogHandler(logger, "INFO") as captor:
+ fonts_to_quadratic(fonts, remember_curve_type=True)
+ assert captor.assertRegex("already converted")
+
+ def test_no_remember_curve_type(self, fonts):
+ assert CURVE_TYPE_LIB_KEY not in fonts[0].lib
+ fonts_to_quadratic(fonts, remember_curve_type=False)
+ assert CURVE_TYPE_LIB_KEY not in fonts[0].lib
+
+ def test_different_glyphsets(self, fonts):
+ del fonts[0]['a']
+ assert 'a' not in fonts[0]
+ assert 'a' in fonts[1]
+ assert fonts_to_quadratic(fonts)
+
+ def test_max_err_em_float(self, fonts):
+ stats = {}
+ fonts_to_quadratic(fonts, max_err_em=0.002, stats=stats)
+ assert stats == {'1': 5, '2': 193, '3': 14}
+
+ def test_max_err_em_list(self, fonts):
+ stats = {}
+ fonts_to_quadratic(fonts, max_err_em=[0.002, 0.002], stats=stats)
+ assert stats == {'1': 5, '2': 193, '3': 14}
+
+ def test_max_err_float(self, fonts):
+ stats = {}
+ fonts_to_quadratic(fonts, max_err=4.096, stats=stats)
+ assert stats == {'1': 5, '2': 193, '3': 14}
+
+ def test_max_err_list(self, fonts):
+ stats = {}
+ fonts_to_quadratic(fonts, max_err=[4.096, 4.096], stats=stats)
+ assert stats == {'1': 5, '2': 193, '3': 14}
+
+ def test_both_max_err_and_max_err_em(self, fonts):
+ with pytest.raises(TypeError, match="Only one .* can be specified"):
+ fonts_to_quadratic(fonts, max_err=1.000, max_err_em=0.001)
+
+ def test_single_font(self, fonts):
+ assert font_to_quadratic(fonts[0], max_err_em=0.002,
+ reverse_direction=True)
+
+
+class GlyphsToQuadraticTest(object):
+
+ @pytest.mark.parametrize(
+ ["glyph", "expected"],
+ [('A', False), # contains no curves, it is not modified
+ ('a', True)],
+ ids=['lines-only', 'has-curves']
+ )
+ def test_modified(self, fonts, glyph, expected):
+ glyphs = [f[glyph] for f in fonts]
+ assert glyphs_to_quadratic(glyphs) == expected
+
+ def test_stats(self, fonts):
+ stats = {}
+ glyphs_to_quadratic([f['a'] for f in fonts], stats=stats)
+ assert stats == {'2': 1, '3': 7, '4': 3, '5': 1}
+
+ def test_max_err_float(self, fonts):
+ glyphs = [f['a'] for f in fonts]
+ stats = {}
+ glyphs_to_quadratic(glyphs, max_err=4.096, stats=stats)
+ assert stats == {'2': 11, '3': 1}
+
+ def test_max_err_list(self, fonts):
+ glyphs = [f['a'] for f in fonts]
+ stats = {}
+ glyphs_to_quadratic(glyphs, max_err=[4.096, 4.096], stats=stats)
+ assert stats == {'2': 11, '3': 1}
+
+ def test_reverse_direction(self, fonts):
+ glyphs = [f['A'] for f in fonts]
+ assert glyphs_to_quadratic(glyphs, reverse_direction=True)
+
+ def test_single_glyph(self, fonts):
+ assert glyph_to_quadratic(fonts[0]['a'], max_err=4.096,
+ reverse_direction=True)
+
+ @pytest.mark.parametrize(
+ ["outlines", "exception", "message"],
+ [
+ [
+ [
+ [
+ ('moveTo', ((0, 0),)),
+ ('curveTo', ((1, 1), (2, 2), (3, 3))),
+ ('curveTo', ((4, 4), (5, 5), (6, 6))),
+ ('closePath', ()),
+ ],
+ [
+ ('moveTo', ((7, 7),)),
+ ('curveTo', ((8, 8), (9, 9), (10, 10))),
+ ('closePath', ()),
+ ]
+ ],
+ IncompatibleSegmentNumberError,
+ "have different number of segments",
+ ],
+ [
+ [
+
+ [
+ ('moveTo', ((0, 0),)),
+ ('curveTo', ((1, 1), (2, 2), (3, 3))),
+ ('closePath', ()),
+ ],
+ [
+ ('moveTo', ((4, 4),)),
+ ('lineTo', ((5, 5),)),
+ ('closePath', ()),
+ ],
+ ],
+ IncompatibleSegmentTypesError,
+ "have incompatible segment types",
+ ],
+ ],
+ ids=[
+ "unequal-length",
+ "different-segment-types",
+ ]
+ )
+ def test_incompatible_glyphs(self, outlines, exception, message):
+ glyphs = []
+ for i, outline in enumerate(outlines):
+ glyph = ufoLib2.objects.Glyph("glyph%d" % i)
+ pen = glyph.getPen()
+ for operator, args in outline:
+ getattr(pen, operator)(*args)
+ glyphs.append(glyph)
+ with pytest.raises(exception) as excinfo:
+ glyphs_to_quadratic(glyphs)
+ assert excinfo.match(message)
+
+ def test_incompatible_fonts(self):
+ font1 = ufoLib2.Font()
+ font1.info.unitsPerEm = 1000
+ glyph1 = font1.newGlyph("a")
+ pen1 = glyph1.getPen()
+ for operator, args in [("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("endPath", ())]:
+ getattr(pen1, operator)(*args)
+
+ font2 = ufoLib2.Font()
+ font2.info.unitsPerEm = 1000
+ glyph2 = font2.newGlyph("a")
+ pen2 = glyph2.getPen()
+ for operator, args in [("moveTo", ((0, 0),)),
+ ("curveTo", ((1, 1), (2, 2), (3, 3))),
+ ("endPath", ())]:
+ getattr(pen2, operator)(*args)
+
+ with pytest.raises(IncompatibleFontsError) as excinfo:
+ fonts_to_quadratic([font1, font2])
+ assert excinfo.match("fonts contains incompatible glyphs: 'a'")
+
+ assert hasattr(excinfo.value, "glyph_errors")
+ error = excinfo.value.glyph_errors['a']
+ assert isinstance(error, IncompatibleSegmentTypesError)
+ assert error.segments == {1: ["line", "curve"]}
+
+ def test_already_quadratic(self):
+ glyph = ufoLib2.objects.Glyph()
+ pen = glyph.getPen()
+ pen.moveTo((0, 0))
+ pen.qCurveTo((1, 1), (2, 2))
+ pen.closePath()
+ assert not glyph_to_quadratic(glyph)
+
+ def test_open_paths(self):
+ glyph = ufoLib2.objects.Glyph()
+ pen = glyph.getPen()
+ pen.moveTo((0, 0))
+ pen.lineTo((1, 1))
+ pen.curveTo((2, 2), (3, 3), (4, 4))
+ pen.endPath()
+ assert glyph_to_quadratic(glyph)
+ # open contour is still open
+ assert glyph[-1][0].segmentType == "move"
+
+ def test_ignore_components(self):
+ glyph = ufoLib2.objects.Glyph()
+ pen = glyph.getPen()
+ pen.addComponent('a', (1, 0, 0, 1, 0, 0))
+ pen.moveTo((0, 0))
+ pen.curveTo((1, 1), (2, 2), (3, 3))
+ pen.closePath()
+ assert glyph_to_quadratic(glyph)
+ assert len(glyph.components) == 1
+
+ def test_overlapping_start_end_points(self):
+ # https://github.com/googlefonts/fontmake/issues/572
+ glyph1 = ufoLib2.objects.Glyph()
+ pen = glyph1.getPointPen()
+ pen.beginPath()
+ pen.addPoint((0, 651), segmentType="line")
+ pen.addPoint((0, 101), segmentType="line")
+ pen.addPoint((0, 101), segmentType="line")
+ pen.addPoint((0, 651), segmentType="line")
+ pen.endPath()
+
+ glyph2 = ufoLib2.objects.Glyph()
+ pen = glyph2.getPointPen()
+ pen.beginPath()
+ pen.addPoint((1, 651), segmentType="line")
+ pen.addPoint((2, 101), segmentType="line")
+ pen.addPoint((3, 101), segmentType="line")
+ pen.addPoint((4, 651), segmentType="line")
+ pen.endPath()
+
+ glyphs = [glyph1, glyph2]
+
+ assert glyphs_to_quadratic(glyphs, reverse_direction=True)
+
+ assert [[(p.x, p.y) for p in glyph[0]] for glyph in glyphs] == [
+ [
+ (0, 651),
+ (0, 651),
+ (0, 101),
+ (0, 101),
+ ],
+ [
+ (1, 651),
+ (4, 651),
+ (3, 101),
+ (2, 101)
+ ],
+ ]
diff --git a/Tests/designspaceLib/data/test.designspace b/Tests/designspaceLib/data/test.designspace
index 901a0ee0..e12f1568 100644
--- a/Tests/designspaceLib/data/test.designspace
+++ b/Tests/designspaceLib/data/test.designspace
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
-<designspace format="4.0">
+<designspace format="4.1">
<axes>
<axis tag="wght" name="weight" minimum="0" maximum="1000" default="0">
<labelname xml:lang="en">Wéíght</labelname>
@@ -13,7 +13,7 @@
<map input="1000" output="990"/>
</axis>
</axes>
- <rules>
+ <rules processing="last">
<rule name="named.rule.1">
<conditionset>
<condition name="axisName_a" minimum="0" maximum="1"/>
diff --git a/Tests/designspaceLib/designspace_test.py b/Tests/designspaceLib/designspace_test.py
index 608ffb1c..8daf741d 100644
--- a/Tests/designspaceLib/designspace_test.py
+++ b/Tests/designspaceLib/designspace_test.py
@@ -1,14 +1,10 @@
# coding=utf-8
-from __future__ import (print_function, division, absolute_import,
- unicode_literals)
-
import os
import sys
import pytest
import warnings
-from fontTools.misc.py23 import open
from fontTools.misc import plistlib
from fontTools.designspaceLib import (
DesignSpaceDocument, SourceDescriptor, AxisDescriptor, RuleDescriptor,
@@ -52,6 +48,7 @@ def test_fill_document(tmpdir):
instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
doc = DesignSpaceDocument()
+ doc.rulesProcessingLast = True
# write some axes
a1 = AxisDescriptor()
@@ -701,6 +698,7 @@ def test_rulesDocument(tmpdir):
testDocPath = os.path.join(tmpdir, "testRules.designspace")
testDocPath2 = os.path.join(tmpdir, "testRules_roundtrip.designspace")
doc = DesignSpaceDocument()
+ doc.rulesProcessingLast = True
a1 = AxisDescriptor()
a1.minimum = 0
a1.maximum = 1000
@@ -744,6 +742,7 @@ def test_rulesDocument(tmpdir):
_addUnwrappedCondition(testDocPath)
doc2 = DesignSpaceDocument()
doc2.read(testDocPath)
+ assert doc2.rulesProcessingLast
assert len(doc2.axes) == 2
assert len(doc2.rules) == 1
assert len(doc2.rules[0].conditionSets) == 2
@@ -949,3 +948,77 @@ def test_loadSourceFonts_no_required_path():
with pytest.raises(DesignSpaceDocumentError, match="no 'path' attribute"):
designspace.loadSourceFonts(lambda p: p)
+
+
+def test_addAxisDescriptor():
+ ds = DesignSpaceDocument()
+
+ axis = ds.addAxisDescriptor(
+ name="Weight", tag="wght", minimum=100, default=400, maximum=900
+ )
+
+ assert ds.axes[0] is axis
+ assert isinstance(axis, AxisDescriptor)
+ assert axis.name == "Weight"
+ assert axis.tag == "wght"
+ assert axis.minimum == 100
+ assert axis.default == 400
+ assert axis.maximum == 900
+
+
+def test_addSourceDescriptor():
+ ds = DesignSpaceDocument()
+
+ source = ds.addSourceDescriptor(name="TestSource", location={"Weight": 400})
+
+ assert ds.sources[0] is source
+ assert isinstance(source, SourceDescriptor)
+ assert source.name == "TestSource"
+ assert source.location == {"Weight": 400}
+
+
+def test_addInstanceDescriptor():
+ ds = DesignSpaceDocument()
+
+ instance = ds.addInstanceDescriptor(
+ name="TestInstance",
+ location={"Weight": 400},
+ styleName="Regular",
+ styleMapStyleName="regular",
+ )
+
+ assert ds.instances[0] is instance
+ assert isinstance(instance, InstanceDescriptor)
+ assert instance.name == "TestInstance"
+ assert instance.location == {"Weight": 400}
+ assert instance.styleName == "Regular"
+ assert instance.styleMapStyleName == "regular"
+
+
+def test_addRuleDescriptor(tmp_path):
+ ds = DesignSpaceDocument()
+
+ rule = ds.addRuleDescriptor(
+ name="TestRule",
+ conditionSets=[
+ [
+ dict(name="Weight", minimum=100, maximum=200),
+ dict(name="Weight", minimum=700, maximum=900),
+ ]
+ ],
+ subs=[("a", "a.alt")],
+ )
+
+ assert ds.rules[0] is rule
+ assert isinstance(rule, RuleDescriptor)
+ assert rule.name == "TestRule"
+ assert rule.conditionSets == [
+ [
+ dict(name="Weight", minimum=100, maximum=200),
+ dict(name="Weight", minimum=700, maximum=900),
+ ]
+ ]
+ assert rule.subs == [("a", "a.alt")]
+
+ # Test it doesn't crash.
+ ds.write(tmp_path / "test.designspace")
diff --git a/Tests/encodings/codecs_test.py b/Tests/encodings/codecs_test.py
index 29e3a0db..9dac416a 100644
--- a/Tests/encodings/codecs_test.py
+++ b/Tests/encodings/codecs_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
import unittest
import fontTools.encodings.codecs # Not to be confused with "import codecs"
@@ -7,19 +5,19 @@ class ExtendedCodecsTest(unittest.TestCase):
def test_decode_mac_japanese(self):
self.assertEqual(b'x\xfe\xfdy'.decode("x_mac_japanese_ttx"),
- unichr(0x78)+unichr(0x2122)+unichr(0x00A9)+unichr(0x79))
+ chr(0x78)+chr(0x2122)+chr(0x00A9)+chr(0x79))
def test_encode_mac_japanese(self):
self.assertEqual(b'x\xfe\xfdy',
- (unichr(0x78)+unichr(0x2122)+unichr(0x00A9)+unichr(0x79)).encode("x_mac_japanese_ttx"))
+ (chr(0x78)+chr(0x2122)+chr(0x00A9)+chr(0x79)).encode("x_mac_japanese_ttx"))
def test_decode_mac_trad_chinese(self):
self.assertEqual(b'\x80'.decode("x_mac_trad_chinese_ttx"),
- unichr(0x5C))
+ chr(0x5C))
def test_decode_mac_romanian(self):
self.assertEqual(b'x\xfb'.decode("mac_romanian"),
- unichr(0x78)+unichr(0x02DA))
+ chr(0x78)+chr(0x02DA))
if __name__ == '__main__':
import sys
diff --git a/Tests/feaLib/STAT2.fea b/Tests/feaLib/STAT2.fea
new file mode 100644
index 00000000..2595a9a4
--- /dev/null
+++ b/Tests/feaLib/STAT2.fea
@@ -0,0 +1,4 @@
+table STAT {
+ ElidedFallbackName { name "Roman"; };
+ DesignAxis zonk 0 { name "Zonkey"; };'
+} STAT;
diff --git a/Tests/feaLib/ast_test.py b/Tests/feaLib/ast_test.py
index 54bd5b3e..4462f052 100644
--- a/Tests/feaLib/ast_test.py
+++ b/Tests/feaLib/ast_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
from fontTools.feaLib import ast
import unittest
@@ -15,6 +13,12 @@ class AstTest(unittest.TestCase):
statement = ast.ValueRecord(xPlacement=10, xAdvance=20)
self.assertEqual(statement.asFea(), "<10 0 20 0>")
+ def test_non_object_location(self):
+ el = ast.Element(location=("file.fea", 1, 2))
+ self.assertEqual(el.location.file, "file.fea")
+ self.assertEqual(el.location.line, 1)
+ self.assertEqual(el.location.column, 2)
+
if __name__ == "__main__":
import sys
diff --git a/Tests/feaLib/builder_test.py b/Tests/feaLib/builder_test.py
index 3d852afd..0a55239c 100644
--- a/Tests/feaLib/builder_test.py
+++ b/Tests/feaLib/builder_test.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.loggingTools import CapturingLogHandler
from fontTools.feaLib.builder import Builder, addOpenTypeFeatures, \
addOpenTypeFeaturesFromString
@@ -10,7 +7,9 @@ from fontTools.feaLib.parser import Parser
from fontTools.feaLib import ast
from fontTools.feaLib.lexer import Lexer
import difflib
+from io import StringIO
import os
+import re
import shutil
import sys
import tempfile
@@ -43,8 +42,9 @@ def makeTTFont():
a_n_d T_h T_h.swash germandbls ydieresis yacute breve
grave acute dieresis macron circumflex cedilla umlaut ogonek caron
damma hamza sukun kasratan lam_meem_jeem noon.final noon.initial
- by feature lookup sub table
+ by feature lookup sub table uni0327 uni0328 e.fina
""".split()
+ glyphs.extend("cid{:05d}".format(cid) for cid in range(800, 1001 + 1))
font = TTFont()
font.setGlyphOrder(glyphs)
return font
@@ -53,7 +53,7 @@ def makeTTFont():
class BuilderTest(unittest.TestCase):
# Feature files in data/*.fea; output gets compared to data/*.ttx.
TEST_FEATURE_FILES = """
- Attach enum markClass language_required
+ Attach cid_range enum markClass language_required
GlyphClassDef LigatureCaretByIndex LigatureCaretByPos
lookup lookupflag feature_aalt ignore_pos
GPOS_1 GPOS_1_zero GPOS_2 GPOS_2b GPOS_3 GPOS_4 GPOS_5 GPOS_6 GPOS_8
@@ -70,8 +70,11 @@ class BuilderTest(unittest.TestCase):
ZeroValue_SinglePos_horizontal ZeroValue_SinglePos_vertical
ZeroValue_PairPos_horizontal ZeroValue_PairPos_vertical
ZeroValue_ChainSinglePos_horizontal ZeroValue_ChainSinglePos_vertical
- PairPosSubtable ChainSubstSubtable ChainPosSubtable LigatureSubtable
- AlternateSubtable MultipleSubstSubtable SingleSubstSubtable
+ PairPosSubtable ChainSubstSubtable SubstSubtable ChainPosSubtable
+ LigatureSubtable AlternateSubtable MultipleSubstSubtable
+ SingleSubstSubtable aalt_chain_contextual_subst AlternateChained
+ MultipleLookupsPerGlyph MultipleLookupsPerGlyph2 GSUB_6_formats
+ GSUB_5_formats delete_glyph STAT_test STAT_test_elidedFallbackNameID
""".split()
def __init__(self, methodName):
@@ -113,12 +116,16 @@ class BuilderTest(unittest.TestCase):
lines.append(line.rstrip() + os.linesep)
return lines
- def expect_ttx(self, font, expected_ttx):
+ def expect_ttx(self, font, expected_ttx, replace=None):
path = self.temp_path(suffix=".ttx")
font.saveXML(path, tables=['head', 'name', 'BASE', 'GDEF', 'GSUB',
- 'GPOS', 'OS/2', 'hhea', 'vhea'])
+ 'GPOS', 'OS/2', 'STAT', 'hhea', 'vhea'])
actual = self.read_ttx(path)
expected = self.read_ttx(expected_ttx)
+ if replace:
+ for i in range(len(expected)):
+ for k, v in replace.items():
+ expected[i] = expected[i].replace(k, v)
if actual != expected:
for line in difflib.unified_diff(
expected, actual, fromfile=expected_ttx, tofile=path):
@@ -132,12 +139,22 @@ class BuilderTest(unittest.TestCase):
def check_feature_file(self, name):
font = makeTTFont()
- addOpenTypeFeatures(font, self.getpath("%s.fea" % name))
+ feapath = self.getpath("%s.fea" % name)
+ addOpenTypeFeatures(font, feapath)
self.expect_ttx(font, self.getpath("%s.ttx" % name))
- # Make sure we can produce binary OpenType tables, not just XML.
+ # Check that:
+ # 1) tables do compile (only G* tables as long as we have a mock font)
+ # 2) dumping after save-reload yields the same TTX dump as before
for tag in ('GDEF', 'GSUB', 'GPOS'):
if tag in font:
- font[tag].compile(font)
+ data = font[tag].compile(font)
+ font[tag].decompile(data, font)
+ self.expect_ttx(font, self.getpath("%s.ttx" % name))
+ # Optionally check a debug dump.
+ debugttx = self.getpath("%s-debug.ttx" % name)
+ if os.path.exists(debugttx):
+ addOpenTypeFeatures(font, feapath, debug=True)
+ self.expect_ttx(font, debugttx, replace = {"__PATH__": feapath})
def check_fea2fea_file(self, name, base=None, parser=Parser):
font = makeTTFont()
@@ -189,6 +206,17 @@ class BuilderTest(unittest.TestCase):
" sub A from [A.alt1 A.alt2];"
"} test;")
+ def test_singleSubst_multipleIdenticalSubstitutionsForSameGlyph_info(self):
+ logger = logging.getLogger("fontTools.feaLib.builder")
+ with CapturingLogHandler(logger, "INFO") as captor:
+ self.build(
+ "feature test {"
+ " sub A by A.sc;"
+ " sub B by B.sc;"
+ " sub A by A.sc;"
+ "} test;")
+ captor.assertRegex('Removing duplicate single substitution from glyph "A" to "A.sc"')
+
def test_multipleSubst_multipleSubstitutionsForSameGlyph(self):
self.assertRaisesRegex(
FeatureLibError,
@@ -197,12 +225,23 @@ class BuilderTest(unittest.TestCase):
"feature test {"
" sub f_f_i by f f i;"
" sub c_t by c t;"
- " sub f_f_i by f f i;"
+ " sub f_f_i by f_f i;"
"} test;")
+ def test_multipleSubst_multipleIdenticalSubstitutionsForSameGlyph_info(self):
+ logger = logging.getLogger("fontTools.feaLib.builder")
+ with CapturingLogHandler(logger, "INFO") as captor:
+ self.build(
+ "feature test {"
+ " sub f_f_i by f f i;"
+ " sub c_t by c t;"
+ " sub f_f_i by f f i;"
+ "} test;")
+ captor.assertRegex(r"Removing duplicate multiple substitution from glyph \"f_f_i\" to \('f', 'f', 'i'\)")
+
def test_pairPos_redefinition_warning(self):
# https://github.com/fonttools/fonttools/issues/1147
- logger = logging.getLogger("fontTools.feaLib.builder")
+ logger = logging.getLogger("fontTools.otlLib.builder")
with CapturingLogHandler(logger, "DEBUG") as captor:
# the pair "yacute semicolon" is redefined in the enum pos
font = self.build(
@@ -315,6 +354,12 @@ class BuilderTest(unittest.TestCase):
"Script statements are not allowed within \"feature size\"",
self.build, "feature size { script latn; } size;")
+ def test_script_in_standalone_lookup(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ "Script statements are not allowed within standalone lookup blocks",
+ self.build, "lookup test { script latn; } test;")
+
def test_language(self):
builder = Builder(makeTTFont(), (None, None))
builder.add_language_system(None, 'latn', 'FRA ')
@@ -342,6 +387,12 @@ class BuilderTest(unittest.TestCase):
"Language statements are not allowed within \"feature size\"",
self.build, "feature size { language FRA; } size;")
+ def test_language_in_standalone_lookup(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ "Language statements are not allowed within standalone lookup blocks",
+ self.build, "lookup test { language FRA; } test;")
+
def test_language_required_duplicate(self):
self.assertRaisesRegex(
FeatureLibError,
@@ -396,6 +447,223 @@ class BuilderTest(unittest.TestCase):
"Lookup blocks cannot be placed inside 'aalt' features",
self.build, "feature aalt {lookup L {} L;} aalt;")
+ def test_chain_subst_refrences_GPOS_looup(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ "Missing index of the specified lookup, might be a positioning lookup",
+ self.build,
+ "lookup dummy { pos a 50; } dummy;"
+ "feature test {"
+ " sub a' lookup dummy b;"
+ "} test;"
+ )
+
+ def test_chain_pos_refrences_GSUB_looup(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ "Missing index of the specified lookup, might be a substitution lookup",
+ self.build,
+ "lookup dummy { sub a by A; } dummy;"
+ "feature test {"
+ " pos a' lookup dummy b;"
+ "} test;"
+ )
+
+ def test_STAT_elidedfallbackname_already_defined(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'ElidedFallbackName is already set.',
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackName { name "Roman"; };'
+ ' ElidedFallbackNameID 256;'
+ '} STAT;')
+
+ def test_STAT_elidedfallbackname_set_twice(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'ElidedFallbackName is already set.',
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackName { name "Roman"; };'
+ ' ElidedFallbackName { name "Italic"; };'
+ '} STAT;')
+
+ def test_STAT_elidedfallbacknameID_already_defined(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'ElidedFallbackNameID is already set.',
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackNameID 256;'
+ ' ElidedFallbackName { name "Roman"; };'
+ '} STAT;')
+
+ def test_STAT_elidedfallbacknameID_not_in_name_table(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'ElidedFallbackNameID 256 points to a nameID that does not '
+ 'exist in the "name" table',
+ self.build,
+ 'table name {'
+ ' nameid 257 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackNameID 256;'
+ ' DesignAxis opsz 1 { name "Optical Size"; };'
+ '} STAT;')
+
+ def test_STAT_design_axis_name(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'Expected "name"',
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackName { name "Roman"; };'
+ ' DesignAxis opsz 0 { badtag "Optical Size"; };'
+ '} STAT;')
+
+ def test_STAT_duplicate_design_axis_name(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'DesignAxis already defined for tag "opsz".',
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackName { name "Roman"; };'
+ ' DesignAxis opsz 0 { name "Optical Size"; };'
+ ' DesignAxis opsz 1 { name "Optical Size"; };'
+ '} STAT;')
+
+ def test_STAT_design_axis_duplicate_order(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ "DesignAxis already defined for axis number 0.",
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackName { name "Roman"; };'
+ ' DesignAxis opsz 0 { name "Optical Size"; };'
+ ' DesignAxis wdth 0 { name "Width"; };'
+ ' AxisValue {'
+ ' location opsz 8;'
+ ' location wdth 400;'
+ ' name "Caption";'
+ ' };'
+ '} STAT;')
+
+ def test_STAT_undefined_tag(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'DesignAxis not defined for wdth.',
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackName { name "Roman"; };'
+ ' DesignAxis opsz 0 { name "Optical Size"; };'
+ ' AxisValue { '
+ ' location wdth 125; '
+ ' name "Wide"; '
+ ' };'
+ '} STAT;')
+
+ def test_STAT_axis_value_format4(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'Axis tag wdth already defined.',
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackName { name "Roman"; };'
+ ' DesignAxis opsz 0 { name "Optical Size"; };'
+ ' DesignAxis wdth 1 { name "Width"; };'
+ ' DesignAxis wght 2 { name "Weight"; };'
+ ' AxisValue { '
+ ' location opsz 8; '
+ ' location wdth 125; '
+ ' location wdth 125; '
+ ' location wght 500; '
+ ' name "Caption Medium Wide"; '
+ ' };'
+ '} STAT;')
+
+ def test_STAT_duplicate_axis_value_record(self):
+ # Test for Duplicate AxisValueRecords even when the definition order
+ # is different.
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'An AxisValueRecord with these values is already defined.',
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackName { name "Roman"; };'
+ ' DesignAxis opsz 0 { name "Optical Size"; };'
+ ' DesignAxis wdth 1 { name "Width"; };'
+ ' AxisValue {'
+ ' location opsz 8;'
+ ' location wdth 400;'
+ ' name "Caption";'
+ ' };'
+ ' AxisValue {'
+ ' location wdth 400;'
+ ' location opsz 8;'
+ ' name "Caption";'
+ ' };'
+ '} STAT;')
+
+ def test_STAT_axis_value_missing_location(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'Expected "Axis location"',
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackName { name "Roman"; '
+ '};'
+ ' DesignAxis opsz 0 { name "Optical Size"; };'
+ ' AxisValue { '
+ ' name "Wide"; '
+ ' };'
+ '} STAT;')
+
+ def test_STAT_invalid_location_tag(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'Tags cannot be longer than 4 characters',
+ self.build,
+ 'table name {'
+ ' nameid 256 "Roman"; '
+ '} name;'
+ 'table STAT {'
+ ' ElidedFallbackName { name "Roman"; '
+ ' name 3 1 0x0411 "ローマン"; }; '
+ ' DesignAxis width 0 { name "Width"; };'
+ '} STAT;')
+
def test_extensions(self):
class ast_BaseClass(ast.MarkClass):
def asFea(self, indent=""):
@@ -504,17 +772,17 @@ class BuilderTest(unittest.TestCase):
assert "GSUB" not in font2
def test_build_unsupported_tables(self):
- self.assertRaises(AssertionError, self.build, "", tables={"FOO"})
+ self.assertRaises(NotImplementedError, self.build, "", tables={"FOO"})
def test_build_pre_parsed_ast_featurefile(self):
- f = UnicodeIO("feature liga {sub f i by f_i;} liga;")
+ f = StringIO("feature liga {sub f i by f_i;} liga;")
tree = Parser(f).parse()
font = makeTTFont()
addOpenTypeFeatures(font, tree)
assert "GSUB" in font
def test_unsupported_subtable_break(self):
- logger = logging.getLogger("fontTools.feaLib.builder")
+ logger = logging.getLogger("fontTools.otlLib.builder")
with CapturingLogHandler(logger, level='WARNING') as captor:
self.build(
"feature test {"
@@ -541,6 +809,26 @@ class BuilderTest(unittest.TestCase):
self.assertIn("GSUB", font)
self.assertNotIn("name", font)
+ def test_singlePos_multiplePositionsForSameGlyph(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ "Already defined different position for glyph",
+ self.build,
+ "lookup foo {"
+ " pos A -45; "
+ " pos A 45; "
+ "} foo;")
+
+ def test_pairPos_enumRuleOverridenBySinglePair_DEBUG(self):
+ logger = logging.getLogger("fontTools.otlLib.builder")
+ with CapturingLogHandler(logger, "DEBUG") as captor:
+ self.build(
+ "feature test {"
+ " enum pos A [V Y] -80;"
+ " pos A V -75;"
+ "} test;")
+ captor.assertRegex('Already defined position for pair A V at')
+
def generate_feature_file_test(name):
return lambda self: self.check_feature_file(name)
diff --git a/Tests/feaLib/data/AlternateChained.fea b/Tests/feaLib/data/AlternateChained.fea
new file mode 100644
index 00000000..45177693
--- /dev/null
+++ b/Tests/feaLib/data/AlternateChained.fea
@@ -0,0 +1,3 @@
+feature test {
+ sub A B a' [Y y] Z from [a.alt1 a.alt2 a.alt3];
+} test;
diff --git a/Tests/feaLib/data/AlternateChained.ttx b/Tests/feaLib/data/AlternateChained.ttx
new file mode 100644
index 00000000..e02bb7d4
--- /dev/null
+++ b/Tests/feaLib/data/AlternateChained.ttx
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <Lookup index="0">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="3">
+ <!-- BacktrackGlyphCount=2 -->
+ <BacktrackCoverage index="0">
+ <Glyph value="B"/>
+ </BacktrackCoverage>
+ <BacktrackCoverage index="1">
+ <Glyph value="A"/>
+ </BacktrackCoverage>
+ <!-- InputGlyphCount=1 -->
+ <InputCoverage index="0">
+ <Glyph value="a"/>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=2 -->
+ <LookAheadCoverage index="0">
+ <Glyph value="Y"/>
+ <Glyph value="y"/>
+ </LookAheadCoverage>
+ <LookAheadCoverage index="1">
+ <Glyph value="Z"/>
+ </LookAheadCoverage>
+ <!-- SubstCount=1 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </SubstLookupRecord>
+ </ChainContextSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="3"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <AlternateSubst index="0">
+ <AlternateSet glyph="a">
+ <Alternate glyph="a.alt1"/>
+ <Alternate glyph="a.alt2"/>
+ <Alternate glyph="a.alt3"/>
+ </AlternateSet>
+ </AlternateSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/feaLib/data/ChainPosSubtable.fea b/Tests/feaLib/data/ChainPosSubtable.fea
index 46506229..e64f8867 100644
--- a/Tests/feaLib/data/ChainPosSubtable.fea
+++ b/Tests/feaLib/data/ChainPosSubtable.fea
@@ -1,7 +1,7 @@
feature test {
- pos X [A-B]' -40 B' -40 A' -40 Y;
+ pos X [A - B]' -40 B' -40 A' -40 Y;
subtable;
pos X A' -111 Y;
subtable;
- pos X B' -40 A' -111 [A-C]' -40 Y;
+ pos X B' -40 A' -111 [A - C]' -40 Y;
} test;
diff --git a/Tests/feaLib/data/ChainPosSubtable.ttx b/Tests/feaLib/data/ChainPosSubtable.ttx
index a780ccb7..695dd862 100644
--- a/Tests/feaLib/data/ChainPosSubtable.ttx
+++ b/Tests/feaLib/data/ChainPosSubtable.ttx
@@ -67,24 +67,26 @@
<LookupListIndex value="1"/>
</PosLookupRecord>
</ChainContextPos>
- <ChainContextPos index="1" Format="3">
- <!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0">
- <Glyph value="X"/>
- </BacktrackCoverage>
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
+ <ChainContextPos index="1" Format="1">
+ <Coverage>
<Glyph value="A"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0">
- <Glyph value="Y"/>
- </LookAheadCoverage>
- <!-- PosCount=1 -->
- <PosLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="2"/>
- </PosLookupRecord>
+ </Coverage>
+ <!-- ChainPosRuleSetCount=1 -->
+ <ChainPosRuleSet index="0">
+ <!-- ChainPosRuleCount=1 -->
+ <ChainPosRule index="0">
+ <!-- BacktrackGlyphCount=1 -->
+ <Backtrack index="0" value="X"/>
+ <!-- InputGlyphCount=1 -->
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAhead index="0" value="Y"/>
+ <!-- PosCount=1 -->
+ <PosLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="2"/>
+ </PosLookupRecord>
+ </ChainPosRule>
+ </ChainPosRuleSet>
</ChainContextPos>
<ChainContextPos index="2" Format="3">
<!-- BacktrackGlyphCount=1 -->
diff --git a/Tests/feaLib/data/ChainSubstSubtable.fea b/Tests/feaLib/data/ChainSubstSubtable.fea
index b6e959a2..ec248805 100644
--- a/Tests/feaLib/data/ChainSubstSubtable.fea
+++ b/Tests/feaLib/data/ChainSubstSubtable.fea
@@ -1,9 +1,9 @@
feature test {
- sub G' by G.swash;
+ sub A G' by G.swash;
subtable;
- sub H' by H.swash;
+ sub A H' by H.swash;
subtable;
- sub G' by g;
+ sub A G' by g;
subtable;
- sub H' by H.swash;
+ sub A H' by H.swash;
} test;
diff --git a/Tests/feaLib/data/ChainSubstSubtable.ttx b/Tests/feaLib/data/ChainSubstSubtable.ttx
index f7a09c7f..75348dc2 100644
--- a/Tests/feaLib/data/ChainSubstSubtable.ttx
+++ b/Tests/feaLib/data/ChainSubstSubtable.ttx
@@ -34,7 +34,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=4 -->
<ChainContextSubst index="0" Format="3">
- <!-- BacktrackGlyphCount=0 -->
+ <!-- BacktrackGlyphCount=1 -->
+ <BacktrackCoverage index="0">
+ <Glyph value="A"/>
+ </BacktrackCoverage>
<!-- InputGlyphCount=1 -->
<InputCoverage index="0">
<Glyph value="G"/>
@@ -47,7 +50,10 @@
</SubstLookupRecord>
</ChainContextSubst>
<ChainContextSubst index="1" Format="3">
- <!-- BacktrackGlyphCount=0 -->
+ <!-- BacktrackGlyphCount=1 -->
+ <BacktrackCoverage index="0">
+ <Glyph value="A"/>
+ </BacktrackCoverage>
<!-- InputGlyphCount=1 -->
<InputCoverage index="0">
<Glyph value="H"/>
@@ -60,7 +66,10 @@
</SubstLookupRecord>
</ChainContextSubst>
<ChainContextSubst index="2" Format="3">
- <!-- BacktrackGlyphCount=0 -->
+ <!-- BacktrackGlyphCount=1 -->
+ <BacktrackCoverage index="0">
+ <Glyph value="A"/>
+ </BacktrackCoverage>
<!-- InputGlyphCount=1 -->
<InputCoverage index="0">
<Glyph value="G"/>
@@ -73,7 +82,10 @@
</SubstLookupRecord>
</ChainContextSubst>
<ChainContextSubst index="3" Format="3">
- <!-- BacktrackGlyphCount=0 -->
+ <!-- BacktrackGlyphCount=1 -->
+ <BacktrackCoverage index="0">
+ <Glyph value="A"/>
+ </BacktrackCoverage>
<!-- InputGlyphCount=1 -->
<InputCoverage index="0">
<Glyph value="H"/>
diff --git a/Tests/feaLib/data/GPOS_2.ttx b/Tests/feaLib/data/GPOS_2.ttx
index 84dc8195..c9a6c146 100644
--- a/Tests/feaLib/data/GPOS_2.ttx
+++ b/Tests/feaLib/data/GPOS_2.ttx
@@ -76,6 +76,7 @@
<!-- Class2Count=2 -->
<Class1Record index="0">
<Class2Record index="0">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
<Value1 XAdvance="-26"/>
diff --git a/Tests/feaLib/data/GPOS_2b.ttx b/Tests/feaLib/data/GPOS_2b.ttx
index 40f458f2..8a892c1e 100644
--- a/Tests/feaLib/data/GPOS_2b.ttx
+++ b/Tests/feaLib/data/GPOS_2b.ttx
@@ -50,6 +50,7 @@
<!-- Class2Count=2 -->
<Class1Record index="0">
<Class2Record index="0">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
<Value1 XAdvance="1"/>
@@ -79,6 +80,7 @@
<!-- Class2Count=3 -->
<Class1Record index="0">
<Class2Record index="0">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
<Value1 XAdvance="4"/>
@@ -89,8 +91,10 @@
</Class1Record>
<Class1Record index="1">
<Class2Record index="0">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="2">
<Value1 XAdvance="2"/>
@@ -114,6 +118,7 @@
<!-- Class2Count=2 -->
<Class1Record index="0">
<Class2Record index="0">
+ <Value1 XPlacement="0" YPlacement="0" XAdvance="0" YAdvance="0"/>
</Class2Record>
<Class2Record index="1">
<Value1 XPlacement="5" YPlacement="5" XAdvance="5" YAdvance="5"/>
diff --git a/Tests/feaLib/data/GPOS_4.fea b/Tests/feaLib/data/GPOS_4.fea
index cfd2d757..7c90ab63 100644
--- a/Tests/feaLib/data/GPOS_4.fea
+++ b/Tests/feaLib/data/GPOS_4.fea
@@ -6,7 +6,11 @@ markClass [cedilla] <anchor 222 22> @BOTTOM_MARKS;
markClass [ogonek] <anchor 333 33> @SIDE_MARKS;
feature test {
- pos base a <anchor 11 1> mark @TOP_MARKS <anchor 12 -1> mark @BOTTOM_MARKS;
- pos base [b c] <anchor 22 -2> mark @BOTTOM_MARKS;
- pos base d <anchor 33 3> mark @SIDE_MARKS;
+ pos base a
+ <anchor 11 1> mark @TOP_MARKS
+ <anchor 12 -1> mark @BOTTOM_MARKS;
+ pos base [b c]
+ <anchor 22 -2> mark @BOTTOM_MARKS;
+ pos base d
+ <anchor 33 3> mark @SIDE_MARKS;
} test;
diff --git a/Tests/feaLib/data/GPOS_5.fea b/Tests/feaLib/data/GPOS_5.fea
index b116539a..a8f8536e 100644
--- a/Tests/feaLib/data/GPOS_5.fea
+++ b/Tests/feaLib/data/GPOS_5.fea
@@ -5,14 +5,29 @@ markClass [ogonek] <anchor 800 -10> @OGONEK;
feature test {
- pos ligature [c_t s_t] <anchor 500 800> mark @TOP_MARKS <anchor 500 -200> mark @BOTTOM_MARKS
- ligComponent <anchor 1500 800> mark @TOP_MARKS <anchor 1500 -200> mark @BOTTOM_MARKS <anchor 1550 0> mark @OGONEK;
+ pos ligature [c_t s_t]
+ <anchor 500 800> mark @TOP_MARKS
+ <anchor 500 -200> mark @BOTTOM_MARKS
+ ligComponent
+ <anchor 1500 800> mark @TOP_MARKS
+ <anchor 1500 -200> mark @BOTTOM_MARKS
+ <anchor 1550 0> mark @OGONEK;
- pos ligature f_l <anchor 300 800> mark @TOP_MARKS <anchor 300 -200> mark @BOTTOM_MARKS
- ligComponent <anchor 600 800> mark @TOP_MARKS <anchor 600 -200> mark @BOTTOM_MARKS;
+ pos ligature f_l
+ <anchor 300 800> mark @TOP_MARKS
+ <anchor 300 -200> mark @BOTTOM_MARKS
+ ligComponent
+ <anchor 600 800> mark @TOP_MARKS
+ <anchor 600 -200> mark @BOTTOM_MARKS;
- pos ligature [f_f_l] <anchor 300 800> mark @TOP_MARKS <anchor 300 -200> mark @BOTTOM_MARKS
- ligComponent <anchor 600 800> mark @TOP_MARKS <anchor 600 -200> mark @BOTTOM_MARKS
- ligComponent <anchor 900 800> mark @TOP_MARKS <anchor 900 -200> mark @BOTTOM_MARKS;
+ pos ligature [f_f_l]
+ <anchor 300 800> mark @TOP_MARKS
+ <anchor 300 -200> mark @BOTTOM_MARKS
+ ligComponent
+ <anchor 600 800> mark @TOP_MARKS
+ <anchor 600 -200> mark @BOTTOM_MARKS
+ ligComponent
+ <anchor 900 800> mark @TOP_MARKS
+ <anchor 900 -200> mark @BOTTOM_MARKS;
} test;
diff --git a/Tests/feaLib/data/GPOS_6.fea b/Tests/feaLib/data/GPOS_6.fea
index 37b29365..e54ff6e3 100644
--- a/Tests/feaLib/data/GPOS_6.fea
+++ b/Tests/feaLib/data/GPOS_6.fea
@@ -5,6 +5,9 @@ markClass macron <anchor 2 2 contourpoint 22> @TOP_MARKS;
markClass [cedilla] <anchor 3 3 contourpoint 33> @BOTTOM_MARKS;
feature test {
- pos mark [acute grave macron ogonek] <anchor 500 200> mark @TOP_MARKS <anchor 500 -80> mark @BOTTOM_MARKS;
- pos mark [dieresis caron] <anchor 500 200> mark @TOP_MARKS;
+ pos mark [acute grave macron ogonek]
+ <anchor 500 200> mark @TOP_MARKS
+ <anchor 500 -80> mark @BOTTOM_MARKS;
+ pos mark [dieresis caron]
+ <anchor 500 200> mark @TOP_MARKS;
} test;
diff --git a/Tests/feaLib/data/GPOS_8.ttx b/Tests/feaLib/data/GPOS_8.ttx
index 86f97560..4d790715 100644
--- a/Tests/feaLib/data/GPOS_8.ttx
+++ b/Tests/feaLib/data/GPOS_8.ttx
@@ -34,42 +34,40 @@
<LookupType value="8"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ChainContextPos index="0" Format="3">
- <!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0">
- <Glyph value="A"/>
- </BacktrackCoverage>
- <!-- InputGlyphCount=4 -->
- <InputCoverage index="0">
- <Glyph value="one"/>
- </InputCoverage>
- <InputCoverage index="1">
- <Glyph value="two"/>
- </InputCoverage>
- <InputCoverage index="2">
+ <ChainContextPos index="0" Format="1">
+ <Coverage>
<Glyph value="one"/>
- </InputCoverage>
- <InputCoverage index="3">
- <Glyph value="two"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- PosCount=4 -->
- <PosLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="1"/>
- </PosLookupRecord>
- <PosLookupRecord index="1">
- <SequenceIndex value="1"/>
- <LookupListIndex value="1"/>
- </PosLookupRecord>
- <PosLookupRecord index="2">
- <SequenceIndex value="2"/>
- <LookupListIndex value="2"/>
- </PosLookupRecord>
- <PosLookupRecord index="3">
- <SequenceIndex value="3"/>
- <LookupListIndex value="2"/>
- </PosLookupRecord>
+ </Coverage>
+ <!-- ChainPosRuleSetCount=1 -->
+ <ChainPosRuleSet index="0">
+ <!-- ChainPosRuleCount=1 -->
+ <ChainPosRule index="0">
+ <!-- BacktrackGlyphCount=1 -->
+ <Backtrack index="0" value="A"/>
+ <!-- InputGlyphCount=4 -->
+ <Input index="0" value="two"/>
+ <Input index="1" value="one"/>
+ <Input index="2" value="two"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- PosCount=4 -->
+ <PosLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </PosLookupRecord>
+ <PosLookupRecord index="1">
+ <SequenceIndex value="1"/>
+ <LookupListIndex value="1"/>
+ </PosLookupRecord>
+ <PosLookupRecord index="2">
+ <SequenceIndex value="2"/>
+ <LookupListIndex value="2"/>
+ </PosLookupRecord>
+ <PosLookupRecord index="3">
+ <SequenceIndex value="3"/>
+ <LookupListIndex value="2"/>
+ </PosLookupRecord>
+ </ChainPosRule>
+ </ChainPosRuleSet>
</ChainContextPos>
</Lookup>
<Lookup index="1">
diff --git a/Tests/feaLib/data/GSUB_5_formats.fea b/Tests/feaLib/data/GSUB_5_formats.fea
new file mode 100644
index 00000000..3acb7edf
--- /dev/null
+++ b/Tests/feaLib/data/GSUB_5_formats.fea
@@ -0,0 +1,20 @@
+lookup GSUB5f1 {
+ ignore sub three four;
+ ignore sub four five;
+} GSUB5f1;
+
+lookup GSUB5f2 {
+ ignore sub [a - z] [A - H] [I - Z];
+ ignore sub [a - z] [A - H] [I - Z];
+ ignore sub [a - z] [I - Z] [A - H];
+} GSUB5f2;
+
+lookup GSUB5f3 {
+ ignore sub e;
+} GSUB5f3;
+
+feature test {
+ lookup GSUB5f1;
+ lookup GSUB5f2;
+ lookup GSUB5f3;
+} test;
diff --git a/Tests/feaLib/data/GSUB_5_formats.ttx b/Tests/feaLib/data/GSUB_5_formats.ttx
new file mode 100644
index 00000000..80196aaa
--- /dev/null
+++ b/Tests/feaLib/data/GSUB_5_formats.ttx
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=3 -->
+ <LookupListIndex index="0" value="0"/>
+ <LookupListIndex index="1" value="1"/>
+ <LookupListIndex index="2" value="2"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=3 -->
+ <Lookup index="0">
+ <LookupType value="5"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ContextSubst index="0" Format="1">
+ <Coverage>
+ <Glyph value="three"/>
+ <Glyph value="four"/>
+ </Coverage>
+ <!-- SubRuleSetCount=2 -->
+ <SubRuleSet index="0">
+ <!-- SubRuleCount=1 -->
+ <SubRule index="0">
+ <!-- GlyphCount=2 -->
+ <!-- SubstCount=0 -->
+ <Input index="0" value="four"/>
+ </SubRule>
+ </SubRuleSet>
+ <SubRuleSet index="1">
+ <!-- SubRuleCount=1 -->
+ <SubRule index="0">
+ <!-- GlyphCount=2 -->
+ <!-- SubstCount=0 -->
+ <Input index="0" value="five"/>
+ </SubRule>
+ </SubRuleSet>
+ </ContextSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="5"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ContextSubst index="0" Format="2">
+ <Coverage>
+ <Glyph value="a"/>
+ <Glyph value="b"/>
+ <Glyph value="c"/>
+ <Glyph value="d"/>
+ <Glyph value="e"/>
+ <Glyph value="f"/>
+ <Glyph value="g"/>
+ <Glyph value="h"/>
+ <Glyph value="i"/>
+ <Glyph value="j"/>
+ <Glyph value="k"/>
+ <Glyph value="l"/>
+ <Glyph value="m"/>
+ <Glyph value="n"/>
+ <Glyph value="o"/>
+ <Glyph value="p"/>
+ <Glyph value="q"/>
+ <Glyph value="r"/>
+ <Glyph value="s"/>
+ <Glyph value="t"/>
+ <Glyph value="u"/>
+ <Glyph value="v"/>
+ <Glyph value="w"/>
+ <Glyph value="x"/>
+ <Glyph value="y"/>
+ <Glyph value="z"/>
+ </Coverage>
+ <ClassDef>
+ <ClassDef glyph="A" class="3"/>
+ <ClassDef glyph="B" class="3"/>
+ <ClassDef glyph="C" class="3"/>
+ <ClassDef glyph="D" class="3"/>
+ <ClassDef glyph="E" class="3"/>
+ <ClassDef glyph="F" class="3"/>
+ <ClassDef glyph="G" class="3"/>
+ <ClassDef glyph="H" class="3"/>
+ <ClassDef glyph="I" class="2"/>
+ <ClassDef glyph="J" class="2"/>
+ <ClassDef glyph="K" class="2"/>
+ <ClassDef glyph="L" class="2"/>
+ <ClassDef glyph="M" class="2"/>
+ <ClassDef glyph="N" class="2"/>
+ <ClassDef glyph="O" class="2"/>
+ <ClassDef glyph="P" class="2"/>
+ <ClassDef glyph="Q" class="2"/>
+ <ClassDef glyph="R" class="2"/>
+ <ClassDef glyph="S" class="2"/>
+ <ClassDef glyph="T" class="2"/>
+ <ClassDef glyph="U" class="2"/>
+ <ClassDef glyph="V" class="2"/>
+ <ClassDef glyph="W" class="2"/>
+ <ClassDef glyph="X" class="2"/>
+ <ClassDef glyph="Y" class="2"/>
+ <ClassDef glyph="Z" class="2"/>
+ <ClassDef glyph="a" class="1"/>
+ <ClassDef glyph="b" class="1"/>
+ <ClassDef glyph="c" class="1"/>
+ <ClassDef glyph="d" class="1"/>
+ <ClassDef glyph="e" class="1"/>
+ <ClassDef glyph="f" class="1"/>
+ <ClassDef glyph="g" class="1"/>
+ <ClassDef glyph="h" class="1"/>
+ <ClassDef glyph="i" class="1"/>
+ <ClassDef glyph="j" class="1"/>
+ <ClassDef glyph="k" class="1"/>
+ <ClassDef glyph="l" class="1"/>
+ <ClassDef glyph="m" class="1"/>
+ <ClassDef glyph="n" class="1"/>
+ <ClassDef glyph="o" class="1"/>
+ <ClassDef glyph="p" class="1"/>
+ <ClassDef glyph="q" class="1"/>
+ <ClassDef glyph="r" class="1"/>
+ <ClassDef glyph="s" class="1"/>
+ <ClassDef glyph="t" class="1"/>
+ <ClassDef glyph="u" class="1"/>
+ <ClassDef glyph="v" class="1"/>
+ <ClassDef glyph="w" class="1"/>
+ <ClassDef glyph="x" class="1"/>
+ <ClassDef glyph="y" class="1"/>
+ <ClassDef glyph="z" class="1"/>
+ </ClassDef>
+ <!-- SubClassSetCount=4 -->
+ <SubClassSet index="0">
+ <!-- SubClassRuleCount=0 -->
+ </SubClassSet>
+ <SubClassSet index="1">
+ <!-- SubClassRuleCount=3 -->
+ <SubClassRule index="0">
+ <!-- GlyphCount=3 -->
+ <!-- SubstCount=0 -->
+ <Class index="0" value="3"/>
+ <Class index="1" value="2"/>
+ </SubClassRule>
+ <SubClassRule index="1">
+ <!-- GlyphCount=3 -->
+ <!-- SubstCount=0 -->
+ <Class index="0" value="3"/>
+ <Class index="1" value="2"/>
+ </SubClassRule>
+ <SubClassRule index="2">
+ <!-- GlyphCount=3 -->
+ <!-- SubstCount=0 -->
+ <Class index="0" value="2"/>
+ <Class index="1" value="3"/>
+ </SubClassRule>
+ </SubClassSet>
+ <SubClassSet index="2">
+ <!-- SubClassRuleCount=0 -->
+ </SubClassSet>
+ <SubClassSet index="3">
+ <!-- SubClassRuleCount=0 -->
+ </SubClassSet>
+ </ContextSubst>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="5"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ContextSubst index="0" Format="3">
+ <!-- GlyphCount=1 -->
+ <!-- SubstCount=0 -->
+ <Coverage index="0">
+ <Glyph value="e"/>
+ </Coverage>
+ </ContextSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/feaLib/data/GSUB_6.fea b/Tests/feaLib/data/GSUB_6.fea
index 82fdac25..22306701 100644
--- a/Tests/feaLib/data/GSUB_6.fea
+++ b/Tests/feaLib/data/GSUB_6.fea
@@ -1,10 +1,10 @@
lookup ChainedSingleSubst {
sub [one two] three A' by A.sc;
- sub [B-D]' seven [eight nine] by [B.sc-D.sc];
+ sub [B - D]' seven [eight nine] by [B.sc - D.sc];
} ChainedSingleSubst;
lookup ChainedMultipleSubst {
- sub [A-C a-c] [D d] E c_t' V [W w] [X-Z x-z] by c t;
+ sub [A - C a - c] [D d] E c_t' V [W w] [X - Z x - z] by c t;
} ChainedMultipleSubst;
lookup ChainedAlternateSubst {
diff --git a/Tests/feaLib/data/GSUB_6.ttx b/Tests/feaLib/data/GSUB_6.ttx
index f32e47d9..18405d25 100644
--- a/Tests/feaLib/data/GSUB_6.ttx
+++ b/Tests/feaLib/data/GSUB_6.ttx
@@ -229,36 +229,30 @@
<LookupType value="6"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ChainContextSubst index="0" Format="3">
- <!-- BacktrackGlyphCount=3 -->
- <BacktrackCoverage index="0">
- <Glyph value="E"/>
- </BacktrackCoverage>
- <BacktrackCoverage index="1">
- <Glyph value="D"/>
- </BacktrackCoverage>
- <BacktrackCoverage index="2">
- <Glyph value="A"/>
- </BacktrackCoverage>
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
+ <ChainContextSubst index="0" Format="1">
+ <Coverage>
<Glyph value="c_t"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=3 -->
- <LookAheadCoverage index="0">
- <Glyph value="V"/>
- </LookAheadCoverage>
- <LookAheadCoverage index="1">
- <Glyph value="W"/>
- </LookAheadCoverage>
- <LookAheadCoverage index="2">
- <Glyph value="X"/>
- </LookAheadCoverage>
- <!-- SubstCount=1 -->
- <SubstLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="2"/>
- </SubstLookupRecord>
+ </Coverage>
+ <!-- ChainSubRuleSetCount=1 -->
+ <ChainSubRuleSet index="0">
+ <!-- ChainSubRuleCount=1 -->
+ <ChainSubRule index="0">
+ <!-- BacktrackGlyphCount=3 -->
+ <Backtrack index="0" value="E"/>
+ <Backtrack index="1" value="D"/>
+ <Backtrack index="2" value="A"/>
+ <!-- InputGlyphCount=1 -->
+ <!-- LookAheadGlyphCount=3 -->
+ <LookAhead index="0" value="V"/>
+ <LookAhead index="1" value="W"/>
+ <LookAhead index="2" value="X"/>
+ <!-- SubstCount=1 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="2"/>
+ </SubstLookupRecord>
+ </ChainSubRule>
+ </ChainSubRuleSet>
</ChainContextSubst>
</Lookup>
</LookupList>
diff --git a/Tests/feaLib/data/GSUB_6_formats.fea b/Tests/feaLib/data/GSUB_6_formats.fea
new file mode 100644
index 00000000..44135d24
--- /dev/null
+++ b/Tests/feaLib/data/GSUB_6_formats.fea
@@ -0,0 +1,20 @@
+lookup GSUB6f1 {
+ ignore sub one two three' four' five six seven;
+ ignore sub two one three' four' six five seven;
+} GSUB6f1;
+
+lookup GSUB6f2 {
+ ignore sub [A - H] [I - Z] [a - z]' [A - H]' [I - Z]';
+ ignore sub [I - Z] [A - H] [a - z]' [A - H]' [I - Z]';
+ ignore sub [A - H] [I - Z] [a - z]' [I - Z]' [A - H]';
+} GSUB6f2;
+
+lookup GSUB6f3 {
+ ignore sub [space comma semicolon] e';
+} GSUB6f3;
+
+feature test {
+ lookup GSUB6f1;
+ lookup GSUB6f2;
+ lookup GSUB6f3;
+} test;
diff --git a/Tests/feaLib/data/GSUB_6_formats.ttx b/Tests/feaLib/data/GSUB_6_formats.ttx
new file mode 100644
index 00000000..ad2a1c5e
--- /dev/null
+++ b/Tests/feaLib/data/GSUB_6_formats.ttx
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=3 -->
+ <LookupListIndex index="0" value="0"/>
+ <LookupListIndex index="1" value="1"/>
+ <LookupListIndex index="2" value="2"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=3 -->
+ <Lookup index="0">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="1">
+ <Coverage>
+ <Glyph value="three"/>
+ </Coverage>
+ <!-- ChainSubRuleSetCount=1 -->
+ <ChainSubRuleSet index="0">
+ <!-- ChainSubRuleCount=2 -->
+ <ChainSubRule index="0">
+ <!-- BacktrackGlyphCount=2 -->
+ <Backtrack index="0" value="two"/>
+ <Backtrack index="1" value="one"/>
+ <!-- InputGlyphCount=2 -->
+ <Input index="0" value="four"/>
+ <!-- LookAheadGlyphCount=3 -->
+ <LookAhead index="0" value="five"/>
+ <LookAhead index="1" value="six"/>
+ <LookAhead index="2" value="seven"/>
+ <!-- SubstCount=0 -->
+ </ChainSubRule>
+ <ChainSubRule index="1">
+ <!-- BacktrackGlyphCount=2 -->
+ <Backtrack index="0" value="one"/>
+ <Backtrack index="1" value="two"/>
+ <!-- InputGlyphCount=2 -->
+ <Input index="0" value="four"/>
+ <!-- LookAheadGlyphCount=3 -->
+ <LookAhead index="0" value="six"/>
+ <LookAhead index="1" value="five"/>
+ <LookAhead index="2" value="seven"/>
+ <!-- SubstCount=0 -->
+ </ChainSubRule>
+ </ChainSubRuleSet>
+ </ChainContextSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="2">
+ <Coverage>
+ <Glyph value="a"/>
+ <Glyph value="b"/>
+ <Glyph value="c"/>
+ <Glyph value="d"/>
+ <Glyph value="e"/>
+ <Glyph value="f"/>
+ <Glyph value="g"/>
+ <Glyph value="h"/>
+ <Glyph value="i"/>
+ <Glyph value="j"/>
+ <Glyph value="k"/>
+ <Glyph value="l"/>
+ <Glyph value="m"/>
+ <Glyph value="n"/>
+ <Glyph value="o"/>
+ <Glyph value="p"/>
+ <Glyph value="q"/>
+ <Glyph value="r"/>
+ <Glyph value="s"/>
+ <Glyph value="t"/>
+ <Glyph value="u"/>
+ <Glyph value="v"/>
+ <Glyph value="w"/>
+ <Glyph value="x"/>
+ <Glyph value="y"/>
+ <Glyph value="z"/>
+ </Coverage>
+ <BacktrackClassDef>
+ <ClassDef glyph="A" class="2"/>
+ <ClassDef glyph="B" class="2"/>
+ <ClassDef glyph="C" class="2"/>
+ <ClassDef glyph="D" class="2"/>
+ <ClassDef glyph="E" class="2"/>
+ <ClassDef glyph="F" class="2"/>
+ <ClassDef glyph="G" class="2"/>
+ <ClassDef glyph="H" class="2"/>
+ <ClassDef glyph="I" class="1"/>
+ <ClassDef glyph="J" class="1"/>
+ <ClassDef glyph="K" class="1"/>
+ <ClassDef glyph="L" class="1"/>
+ <ClassDef glyph="M" class="1"/>
+ <ClassDef glyph="N" class="1"/>
+ <ClassDef glyph="O" class="1"/>
+ <ClassDef glyph="P" class="1"/>
+ <ClassDef glyph="Q" class="1"/>
+ <ClassDef glyph="R" class="1"/>
+ <ClassDef glyph="S" class="1"/>
+ <ClassDef glyph="T" class="1"/>
+ <ClassDef glyph="U" class="1"/>
+ <ClassDef glyph="V" class="1"/>
+ <ClassDef glyph="W" class="1"/>
+ <ClassDef glyph="X" class="1"/>
+ <ClassDef glyph="Y" class="1"/>
+ <ClassDef glyph="Z" class="1"/>
+ </BacktrackClassDef>
+ <InputClassDef>
+ <ClassDef glyph="A" class="3"/>
+ <ClassDef glyph="B" class="3"/>
+ <ClassDef glyph="C" class="3"/>
+ <ClassDef glyph="D" class="3"/>
+ <ClassDef glyph="E" class="3"/>
+ <ClassDef glyph="F" class="3"/>
+ <ClassDef glyph="G" class="3"/>
+ <ClassDef glyph="H" class="3"/>
+ <ClassDef glyph="I" class="2"/>
+ <ClassDef glyph="J" class="2"/>
+ <ClassDef glyph="K" class="2"/>
+ <ClassDef glyph="L" class="2"/>
+ <ClassDef glyph="M" class="2"/>
+ <ClassDef glyph="N" class="2"/>
+ <ClassDef glyph="O" class="2"/>
+ <ClassDef glyph="P" class="2"/>
+ <ClassDef glyph="Q" class="2"/>
+ <ClassDef glyph="R" class="2"/>
+ <ClassDef glyph="S" class="2"/>
+ <ClassDef glyph="T" class="2"/>
+ <ClassDef glyph="U" class="2"/>
+ <ClassDef glyph="V" class="2"/>
+ <ClassDef glyph="W" class="2"/>
+ <ClassDef glyph="X" class="2"/>
+ <ClassDef glyph="Y" class="2"/>
+ <ClassDef glyph="Z" class="2"/>
+ <ClassDef glyph="a" class="1"/>
+ <ClassDef glyph="b" class="1"/>
+ <ClassDef glyph="c" class="1"/>
+ <ClassDef glyph="d" class="1"/>
+ <ClassDef glyph="e" class="1"/>
+ <ClassDef glyph="f" class="1"/>
+ <ClassDef glyph="g" class="1"/>
+ <ClassDef glyph="h" class="1"/>
+ <ClassDef glyph="i" class="1"/>
+ <ClassDef glyph="j" class="1"/>
+ <ClassDef glyph="k" class="1"/>
+ <ClassDef glyph="l" class="1"/>
+ <ClassDef glyph="m" class="1"/>
+ <ClassDef glyph="n" class="1"/>
+ <ClassDef glyph="o" class="1"/>
+ <ClassDef glyph="p" class="1"/>
+ <ClassDef glyph="q" class="1"/>
+ <ClassDef glyph="r" class="1"/>
+ <ClassDef glyph="s" class="1"/>
+ <ClassDef glyph="t" class="1"/>
+ <ClassDef glyph="u" class="1"/>
+ <ClassDef glyph="v" class="1"/>
+ <ClassDef glyph="w" class="1"/>
+ <ClassDef glyph="x" class="1"/>
+ <ClassDef glyph="y" class="1"/>
+ <ClassDef glyph="z" class="1"/>
+ </InputClassDef>
+ <LookAheadClassDef>
+ </LookAheadClassDef>
+ <!-- ChainSubClassSetCount=4 -->
+ <ChainSubClassSet index="0">
+ <!-- ChainSubClassRuleCount=0 -->
+ </ChainSubClassSet>
+ <ChainSubClassSet index="1">
+ <!-- ChainSubClassRuleCount=3 -->
+ <ChainSubClassRule index="0">
+ <!-- BacktrackGlyphCount=2 -->
+ <Backtrack index="0" value="1"/>
+ <Backtrack index="1" value="2"/>
+ <!-- InputGlyphCount=3 -->
+ <Input index="0" value="3"/>
+ <Input index="1" value="2"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=0 -->
+ </ChainSubClassRule>
+ <ChainSubClassRule index="1">
+ <!-- BacktrackGlyphCount=2 -->
+ <Backtrack index="0" value="2"/>
+ <Backtrack index="1" value="1"/>
+ <!-- InputGlyphCount=3 -->
+ <Input index="0" value="3"/>
+ <Input index="1" value="2"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=0 -->
+ </ChainSubClassRule>
+ <ChainSubClassRule index="2">
+ <!-- BacktrackGlyphCount=2 -->
+ <Backtrack index="0" value="1"/>
+ <Backtrack index="1" value="2"/>
+ <!-- InputGlyphCount=3 -->
+ <Input index="0" value="2"/>
+ <Input index="1" value="3"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=0 -->
+ </ChainSubClassRule>
+ </ChainSubClassSet>
+ <ChainSubClassSet index="2">
+ <!-- ChainSubClassRuleCount=0 -->
+ </ChainSubClassSet>
+ <ChainSubClassSet index="3">
+ <!-- ChainSubClassRuleCount=0 -->
+ </ChainSubClassSet>
+ </ChainContextSubst>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="3">
+ <!-- BacktrackGlyphCount=1 -->
+ <BacktrackCoverage index="0">
+ <Glyph value="space"/>
+ <Glyph value="semicolon"/>
+ <Glyph value="comma"/>
+ </BacktrackCoverage>
+ <!-- InputGlyphCount=1 -->
+ <InputCoverage index="0">
+ <Glyph value="e"/>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=0 -->
+ </ChainContextSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/feaLib/data/GSUB_8.fea b/Tests/feaLib/data/GSUB_8.fea
index 62c7bee4..8f417496 100644
--- a/Tests/feaLib/data/GSUB_8.fea
+++ b/Tests/feaLib/data/GSUB_8.fea
@@ -2,7 +2,7 @@ languagesystem DFLT dflt;
feature test {
rsub [a A] [b B] [c C] q' [d D] [e E] [f F] by Q;
- rsub [a A] [b B] [c C] [s-z]' [d D] [e E] [f F] by [S-Z];
+ rsub [a A] [b B] [c C] [s - z]' [d D] [e E] [f F] by [S - Z];
# Having no context for a reverse chaining substitution rule
# is a little degenerate (we define a chain without linking it
diff --git a/Tests/feaLib/data/GSUB_error.fea b/Tests/feaLib/data/GSUB_error.fea
new file mode 100644
index 00000000..ae175c50
--- /dev/null
+++ b/Tests/feaLib/data/GSUB_error.fea
@@ -0,0 +1,13 @@
+# Trigger a parser error in the function parse_substitute_ in order to improve the error message.
+# Note that this is not a valid substitution, this test is made to trigger an error.
+
+languagesystem latn dflt;
+
+@base = [a e];
+@accents = [acute grave];
+
+feature abvs {
+ lookup lookup1 {
+ sub @base @accents by @base;
+ } lookup1;
+} abvs;
diff --git a/Tests/feaLib/data/LigatureCaretByIndex.fea b/Tests/feaLib/data/LigatureCaretByIndex.fea
index 19681724..5f74fc64 100644
--- a/Tests/feaLib/data/LigatureCaretByIndex.fea
+++ b/Tests/feaLib/data/LigatureCaretByIndex.fea
@@ -1,10 +1,6 @@
table GDEF {
LigatureCaretByIndex [c_t s_t] 11;
- # The OpenType Feature File specification does not define what should
- # happen when there are multiple LigatureCaretByIndex statements for
- # the same glyph. Our behavior matches that of Adobe makeotf v2.0.90.
- # https://github.com/adobe-type-tools/afdko/issues/95
LigatureCaretByIndex o_f_f_i 66 33;
LigatureCaretByIndex o_f_f_i 55;
} GDEF;
diff --git a/Tests/feaLib/data/LigatureCaretByIndex.ttx b/Tests/feaLib/data/LigatureCaretByIndex.ttx
index a7580772..f3a378c9 100644
--- a/Tests/feaLib/data/LigatureCaretByIndex.ttx
+++ b/Tests/feaLib/data/LigatureCaretByIndex.ttx
@@ -17,14 +17,11 @@
</CaretValue>
</LigGlyph>
<LigGlyph index="1">
- <!-- CaretCount=3 -->
+ <!-- CaretCount=2 -->
<CaretValue index="0" Format="2">
<CaretValuePoint value="33"/>
</CaretValue>
<CaretValue index="1" Format="2">
- <CaretValuePoint value="55"/>
- </CaretValue>
- <CaretValue index="2" Format="2">
<CaretValuePoint value="66"/>
</CaretValue>
</LigGlyph>
diff --git a/Tests/feaLib/data/LigatureCaretByPos.fea b/Tests/feaLib/data/LigatureCaretByPos.fea
index a8ccf6f9..e799c3c2 100644
--- a/Tests/feaLib/data/LigatureCaretByPos.fea
+++ b/Tests/feaLib/data/LigatureCaretByPos.fea
@@ -1,10 +1,6 @@
table GDEF {
LigatureCaretByPos [c_h c_k] 500;
- # The OpenType Feature File specification does not define what should
- # happen when there are multiple LigatureCaretByPos statements for
- # the same glyph. Our behavior matches that of Adobe makeotf v2.0.90.
- # https://github.com/adobe-type-tools/afdko/issues/95
LigatureCaretByPos o_f_f_i 700 300;
LigatureCaretByPos o_f_f_i 900;
} GDEF;
diff --git a/Tests/feaLib/data/LigatureCaretByPos.ttx b/Tests/feaLib/data/LigatureCaretByPos.ttx
index 911db6b2..8c0bf8cb 100644
--- a/Tests/feaLib/data/LigatureCaretByPos.ttx
+++ b/Tests/feaLib/data/LigatureCaretByPos.ttx
@@ -23,16 +23,13 @@
</CaretValue>
</LigGlyph>
<LigGlyph index="2">
- <!-- CaretCount=3 -->
+ <!-- CaretCount=2 -->
<CaretValue index="0" Format="1">
<Coordinate value="300"/>
</CaretValue>
<CaretValue index="1" Format="1">
<Coordinate value="700"/>
</CaretValue>
- <CaretValue index="2" Format="1">
- <Coordinate value="900"/>
- </CaretValue>
</LigGlyph>
</LigCaretList>
</GDEF>
diff --git a/Tests/feaLib/data/MultipleLookupsPerGlyph.fea b/Tests/feaLib/data/MultipleLookupsPerGlyph.fea
new file mode 100644
index 00000000..e0c22226
--- /dev/null
+++ b/Tests/feaLib/data/MultipleLookupsPerGlyph.fea
@@ -0,0 +1,11 @@
+lookup a_to_bc {
+ sub a by b c;
+} a_to_bc;
+
+lookup b_to_d {
+ sub b by d;
+} b_to_d;
+
+feature test {
+ sub a' lookup a_to_bc lookup b_to_d b;
+} test; \ No newline at end of file
diff --git a/Tests/feaLib/data/MultipleLookupsPerGlyph.ttx b/Tests/feaLib/data/MultipleLookupsPerGlyph.ttx
new file mode 100644
index 00000000..927694cb
--- /dev/null
+++ b/Tests/feaLib/data/MultipleLookupsPerGlyph.ttx
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="2"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=3 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MultipleSubst index="0">
+ <Substitution in="a" out="b,c"/>
+ </MultipleSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="b" out="d"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="3">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=1 -->
+ <InputCoverage index="0">
+ <Glyph value="a"/>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAheadCoverage index="0">
+ <Glyph value="b"/>
+ </LookAheadCoverage>
+ <!-- SubstCount=2 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="0"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="1">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </SubstLookupRecord>
+ </ChainContextSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/feaLib/data/MultipleLookupsPerGlyph2.fea b/Tests/feaLib/data/MultipleLookupsPerGlyph2.fea
new file mode 100644
index 00000000..5a9d19b2
--- /dev/null
+++ b/Tests/feaLib/data/MultipleLookupsPerGlyph2.fea
@@ -0,0 +1,11 @@
+lookup a_reduce_sb {
+ pos a <-80 0 -160 0>;
+} a_reduce_sb;
+
+lookup a_raise {
+ pos a <0 100 0 0>;
+} a_raise;
+
+feature test {
+ pos a' lookup a_reduce_sb lookup a_raise b;
+} test; \ No newline at end of file
diff --git a/Tests/feaLib/data/MultipleLookupsPerGlyph2.ttx b/Tests/feaLib/data/MultipleLookupsPerGlyph2.ttx
new file mode 100644
index 00000000..008d95b6
--- /dev/null
+++ b/Tests/feaLib/data/MultipleLookupsPerGlyph2.ttx
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="2"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=3 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="a"/>
+ </Coverage>
+ <ValueFormat value="5"/>
+ <Value XPlacement="-80" XAdvance="-160"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="a"/>
+ </Coverage>
+ <ValueFormat value="2"/>
+ <Value YPlacement="100"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="8"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextPos index="0" Format="3">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=1 -->
+ <InputCoverage index="0">
+ <Glyph value="a"/>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAheadCoverage index="0">
+ <Glyph value="b"/>
+ </LookAheadCoverage>
+ <!-- PosCount=2 -->
+ <PosLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="0"/>
+ </PosLookupRecord>
+ <PosLookupRecord index="1">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </PosLookupRecord>
+ </ChainContextPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/feaLib/data/PairPosSubtable.ttx b/Tests/feaLib/data/PairPosSubtable.ttx
index 4b76f991..2d78f64f 100644
--- a/Tests/feaLib/data/PairPosSubtable.ttx
+++ b/Tests/feaLib/data/PairPosSubtable.ttx
@@ -76,6 +76,7 @@
<!-- Class2Count=2 -->
<Class1Record index="0">
<Class2Record index="0">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
<Value1 XAdvance="-12"/>
@@ -105,8 +106,10 @@
<!-- Class2Count=3 -->
<Class1Record index="0">
<Class2Record index="0">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="2">
<Value1 XAdvance="-20"/>
@@ -114,11 +117,13 @@
</Class1Record>
<Class1Record index="1">
<Class2Record index="0">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
<Value1 XAdvance="-10"/>
</Class2Record>
<Class2Record index="2">
+ <Value1 XAdvance="0"/>
</Class2Record>
</Class1Record>
</PairPos>
diff --git a/Tests/feaLib/data/STAT_bad.fea b/Tests/feaLib/data/STAT_bad.fea
new file mode 100644
index 00000000..8ec887f0
--- /dev/null
+++ b/Tests/feaLib/data/STAT_bad.fea
@@ -0,0 +1,96 @@
+# bad fea file: Testing DesignAxis tag with incorrect label
+table name {
+ nameid 25 "TestFont";
+} name;
+
+
+table STAT {
+
+ ElidedFallbackName { name "Roman"; };
+
+ DesignAxis opsz 0 { badtag "Optical Size"; }; #'badtag' instead of 'name' is incorrect
+ DesignAxis wdth 1 { name "Width"; };
+ DesignAxis wght 2 { name "Weight"; };
+ DesignAxis ital 3 { name "Italic"; };
+
+ AxisValue {
+ location opsz 8 5 9;
+ location wdth 300 350 450;
+ name "Caption";
+ };
+
+ AxisValue {
+ location opsz 11 9 12;
+ name "Text";
+ flag OlderSiblingFontAttribute ElidableAxisValueName ;
+ };
+
+ AxisValue {
+ location opsz 16.7 12 24;
+ name "Subhead";
+ };
+
+ AxisValue {
+ location opsz 72 24 72;
+ name "Display";
+ };
+
+ AxisValue {
+ location wdth 80 80 89;
+ name "Condensed";
+ };
+
+ AxisValue {
+ location wdth 90 90 96;
+ name "Semicondensed";
+ };
+
+ AxisValue {
+ location wdth 100 97 101;
+ name "Normal";
+ flag ElidableAxisValueName;
+ };
+
+ AxisValue {
+ location wdth 125 102 125;
+ name "Extended";
+ };
+
+ AxisValue {
+ location wght 300 300 349;
+ name "Light";
+ };
+
+ AxisValue {
+ location wght 400 350 449;
+ name "Regular";
+ flag ElidableAxisValueName;
+ };
+
+ AxisValue {
+ location wght 500 450 549;
+ name "Medium";
+ };
+
+ AxisValue {
+ location wght 600 550 649;
+ name "Semibold";
+ };
+
+ AxisValue {
+ location wght 700 650 749;
+ name "Bold";
+ };
+
+ AxisValue {
+ location wght 900 750 900;
+ name "Black";
+ };
+
+ AxisValue {
+ location ital 0;
+ name "Roman";
+ flag ElidableAxisValueName;
+ };
+
+} STAT;
diff --git a/Tests/feaLib/data/STAT_test.fea b/Tests/feaLib/data/STAT_test.fea
new file mode 100644
index 00000000..01036376
--- /dev/null
+++ b/Tests/feaLib/data/STAT_test.fea
@@ -0,0 +1,109 @@
+table name {
+ nameid 25 "TestFont";
+} name;
+
+
+table STAT {
+
+ ElidedFallbackName {
+ name "Roman";
+ name 3 1 1041 "ローマン";
+ };
+
+ DesignAxis opsz 0 {
+ name "Optical Size";
+ };
+
+ DesignAxis wdth 1 {
+ name "Width";
+ };
+
+ DesignAxis wght 2 {
+ name "Weight";
+ };
+
+ DesignAxis ital 3 {
+ name "Italic";
+ }; # here comment
+
+ AxisValue {
+ location opsz 8; # comment here
+ location wdth 400; # another comment
+ name "Caption"; # more comments
+ };
+
+ AxisValue {
+ location opsz 11 9 12;
+ name "Text";
+ flag OlderSiblingFontAttribute ElidableAxisValueName;
+ };
+
+ AxisValue {
+ location opsz 16.7 12 24;
+ name "Subhead";
+ };
+
+ AxisValue {
+ location opsz 72 24 72;
+ name "Display";
+ };
+
+ AxisValue {
+ location wdth 80 80 89;
+ name "Condensed";
+ };
+
+ AxisValue {
+ location wdth 90 90 96;
+ name "Semicondensed";
+ };
+
+ AxisValue {
+ location wdth 100 97 101;
+ name "Normal";
+ flag ElidableAxisValueName;
+ };
+
+ AxisValue {
+ location wdth 125 102 125;
+ name "Extended";
+ };
+
+ AxisValue {
+ location wght 300 300 349;
+ name "Light";
+ };
+
+ AxisValue {
+ location wght 400 350 449;
+ name "Regular";
+ flag ElidableAxisValueName;
+ };
+
+ AxisValue {
+ location wght 500 450 549;
+ name "Medium";
+ };
+
+ AxisValue {
+ location wght 600 550 649;
+ name "Semibold";
+ };
+
+ AxisValue {
+ location wght 700 650 749;
+ name "Bold";
+ };
+
+ AxisValue {
+ location wght 900 750 900;
+ name "Black";
+ };
+
+ AxisValue {
+ location ital 0;
+ name "Roman";
+ flag ElidableAxisValueName; # flag comment
+ };
+
+} STAT;
diff --git a/Tests/feaLib/data/STAT_test.ttx b/Tests/feaLib/data/STAT_test.ttx
new file mode 100644
index 00000000..d1b2b697
--- /dev/null
+++ b/Tests/feaLib/data/STAT_test.ttx
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
+
+ <name>
+ <namerecord nameID="25" platformID="3" platEncID="1" langID="0x409">
+ TestFont
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Roman
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x411">
+ ローマン
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ Optical Size
+ </namerecord>
+ <namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
+ Text
+ </namerecord>
+ <namerecord nameID="259" platformID="3" platEncID="1" langID="0x409">
+ Subhead
+ </namerecord>
+ <namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
+ Display
+ </namerecord>
+ <namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
+ Width
+ </namerecord>
+ <namerecord nameID="262" platformID="3" platEncID="1" langID="0x409">
+ Condensed
+ </namerecord>
+ <namerecord nameID="263" platformID="3" platEncID="1" langID="0x409">
+ Semicondensed
+ </namerecord>
+ <namerecord nameID="264" platformID="3" platEncID="1" langID="0x409">
+ Normal
+ </namerecord>
+ <namerecord nameID="265" platformID="3" platEncID="1" langID="0x409">
+ Extended
+ </namerecord>
+ <namerecord nameID="266" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ <namerecord nameID="267" platformID="3" platEncID="1" langID="0x409">
+ Light
+ </namerecord>
+ <namerecord nameID="268" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="269" platformID="3" platEncID="1" langID="0x409">
+ Medium
+ </namerecord>
+ <namerecord nameID="270" platformID="3" platEncID="1" langID="0x409">
+ Semibold
+ </namerecord>
+ <namerecord nameID="271" platformID="3" platEncID="1" langID="0x409">
+ Bold
+ </namerecord>
+ <namerecord nameID="272" platformID="3" platEncID="1" langID="0x409">
+ Black
+ </namerecord>
+ <namerecord nameID="273" platformID="3" platEncID="1" langID="0x409">
+ Italic
+ </namerecord>
+ <namerecord nameID="274" platformID="3" platEncID="1" langID="0x409">
+ Roman
+ </namerecord>
+ <namerecord nameID="275" platformID="3" platEncID="1" langID="0x409">
+ Caption
+ </namerecord>
+ </name>
+
+ <STAT>
+ <Version value="0x00010002"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=4 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="opsz"/>
+ <AxisNameID value="257"/> <!-- Optical Size -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ <Axis index="1">
+ <AxisTag value="wdth"/>
+ <AxisNameID value="261"/> <!-- Width -->
+ <AxisOrdering value="1"/>
+ </Axis>
+ <Axis index="2">
+ <AxisTag value="wght"/>
+ <AxisNameID value="266"/> <!-- Weight -->
+ <AxisOrdering value="2"/>
+ </Axis>
+ <Axis index="3">
+ <AxisTag value="ital"/>
+ <AxisNameID value="273"/> <!-- Italic -->
+ <AxisOrdering value="3"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=15 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="4">
+ <!-- AxisCount=2 -->
+ <Flags value="0"/>
+ <ValueNameID value="275"/> <!-- Caption -->
+ <AxisValueRecord index="0">
+ <AxisIndex value="0"/>
+ <Value value="8.0"/>
+ </AxisValueRecord>
+ <AxisValueRecord index="1">
+ <AxisIndex value="1"/>
+ <Value value="400.0"/>
+ </AxisValueRecord>
+ </AxisValue>
+ <AxisValue index="1" Format="2">
+ <AxisIndex value="0"/>
+ <Flags value="3"/> <!-- OlderSiblingFontAttribute ElidableAxisValueName -->
+ <ValueNameID value="258"/> <!-- Text -->
+ <NominalValue value="11.0"/>
+ <RangeMinValue value="9.0"/>
+ <RangeMaxValue value="12.0"/>
+ </AxisValue>
+ <AxisValue index="2" Format="2">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="259"/> <!-- Subhead -->
+ <NominalValue value="16.7"/>
+ <RangeMinValue value="12.0"/>
+ <RangeMaxValue value="24.0"/>
+ </AxisValue>
+ <AxisValue index="3" Format="2">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="260"/> <!-- Display -->
+ <NominalValue value="72.0"/>
+ <RangeMinValue value="24.0"/>
+ <RangeMaxValue value="72.0"/>
+ </AxisValue>
+ <AxisValue index="4" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="262"/> <!-- Condensed -->
+ <NominalValue value="80.0"/>
+ <RangeMinValue value="80.0"/>
+ <RangeMaxValue value="89.0"/>
+ </AxisValue>
+ <AxisValue index="5" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="263"/> <!-- Semicondensed -->
+ <NominalValue value="90.0"/>
+ <RangeMinValue value="90.0"/>
+ <RangeMaxValue value="96.0"/>
+ </AxisValue>
+ <AxisValue index="6" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="264"/> <!-- Normal -->
+ <NominalValue value="100.0"/>
+ <RangeMinValue value="97.0"/>
+ <RangeMaxValue value="101.0"/>
+ </AxisValue>
+ <AxisValue index="7" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="265"/> <!-- Extended -->
+ <NominalValue value="125.0"/>
+ <RangeMinValue value="102.0"/>
+ <RangeMaxValue value="125.0"/>
+ </AxisValue>
+ <AxisValue index="8" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="267"/> <!-- Light -->
+ <NominalValue value="300.0"/>
+ <RangeMinValue value="300.0"/>
+ <RangeMaxValue value="349.0"/>
+ </AxisValue>
+ <AxisValue index="9" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="268"/> <!-- Regular -->
+ <NominalValue value="400.0"/>
+ <RangeMinValue value="350.0"/>
+ <RangeMaxValue value="449.0"/>
+ </AxisValue>
+ <AxisValue index="10" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="269"/> <!-- Medium -->
+ <NominalValue value="500.0"/>
+ <RangeMinValue value="450.0"/>
+ <RangeMaxValue value="549.0"/>
+ </AxisValue>
+ <AxisValue index="11" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="270"/> <!-- Semibold -->
+ <NominalValue value="600.0"/>
+ <RangeMinValue value="550.0"/>
+ <RangeMaxValue value="649.0"/>
+ </AxisValue>
+ <AxisValue index="12" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="271"/> <!-- Bold -->
+ <NominalValue value="700.0"/>
+ <RangeMinValue value="650.0"/>
+ <RangeMaxValue value="749.0"/>
+ </AxisValue>
+ <AxisValue index="13" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="272"/> <!-- Black -->
+ <NominalValue value="900.0"/>
+ <RangeMinValue value="750.0"/>
+ <RangeMaxValue value="900.0"/>
+ </AxisValue>
+ <AxisValue index="14" Format="1">
+ <AxisIndex value="3"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="274"/> <!-- Roman -->
+ <Value value="0.0"/>
+ </AxisValue>
+ </AxisValueArray>
+ <ElidedFallbackNameID value="256"/> <!-- Roman -->
+ </STAT>
+
+</ttFont>
diff --git a/Tests/feaLib/data/STAT_test_elidedFallbackNameID.fea b/Tests/feaLib/data/STAT_test_elidedFallbackNameID.fea
new file mode 100644
index 00000000..5a141803
--- /dev/null
+++ b/Tests/feaLib/data/STAT_test_elidedFallbackNameID.fea
@@ -0,0 +1,84 @@
+table name {
+ nameid 25 "TestFont";
+ nameid 256 "Roman";
+} name;
+table STAT {
+ ElidedFallbackNameID 256;
+ DesignAxis opsz 0 {
+ name "Optical Size";
+ };
+ DesignAxis wdth 1 {
+ name "Width";
+ };
+ DesignAxis wght 2 {
+ name "Weight";
+ };
+ DesignAxis ital 3 {
+ name "Italic";
+ }; # here comment
+ AxisValue {
+ location opsz 8; # comment here
+ location wdth 400; # another comment
+ name "Caption"; # more comments
+ };
+ AxisValue {
+ location opsz 11 9 12;
+ name "Text";
+ flag OlderSiblingFontAttribute ElidableAxisValueName;
+ };
+ AxisValue {
+ location opsz 16.7 12 24;
+ name "Subhead";
+ };
+ AxisValue {
+ location opsz 72 24 72;
+ name "Display";
+ };
+ AxisValue {
+ location wdth 80 80 89;
+ name "Condensed";
+ };
+ AxisValue {
+ location wdth 90 90 96;
+ name "Semicondensed";
+ };
+ AxisValue {
+ location wdth 100 97 101;
+ name "Normal";
+ flag ElidableAxisValueName;
+ };
+ AxisValue {
+ location wdth 125 102 125;
+ name "Extended";
+ };
+ AxisValue {
+ location wght 300 300 349;
+ name "Light";
+ };
+ AxisValue {
+ location wght 400 350 449;
+ name "Regular";
+ flag ElidableAxisValueName;
+ };
+ AxisValue {
+ location wght 500 450 549;
+ name "Medium";
+ };
+ AxisValue {
+ location wght 600 550 649;
+ name "Semibold";
+ };
+ AxisValue {
+ location wght 700 650 749;
+ name "Bold";
+ };
+ AxisValue {
+ location wght 900 750 900;
+ name "Black";
+ };
+ AxisValue {
+ location ital 0;
+ name "Roman";
+ flag ElidableAxisValueName; # flag comment
+ };
+} STAT; \ No newline at end of file
diff --git a/Tests/feaLib/data/STAT_test_elidedFallbackNameID.ttx b/Tests/feaLib/data/STAT_test_elidedFallbackNameID.ttx
new file mode 100644
index 00000000..32802e0f
--- /dev/null
+++ b/Tests/feaLib/data/STAT_test_elidedFallbackNameID.ttx
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
+
+ <name>
+ <namerecord nameID="25" platformID="3" platEncID="1" langID="0x409">
+ TestFont
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Roman
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ Optical Size
+ </namerecord>
+ <namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
+ Text
+ </namerecord>
+ <namerecord nameID="259" platformID="3" platEncID="1" langID="0x409">
+ Subhead
+ </namerecord>
+ <namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
+ Display
+ </namerecord>
+ <namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
+ Width
+ </namerecord>
+ <namerecord nameID="262" platformID="3" platEncID="1" langID="0x409">
+ Condensed
+ </namerecord>
+ <namerecord nameID="263" platformID="3" platEncID="1" langID="0x409">
+ Semicondensed
+ </namerecord>
+ <namerecord nameID="264" platformID="3" platEncID="1" langID="0x409">
+ Normal
+ </namerecord>
+ <namerecord nameID="265" platformID="3" platEncID="1" langID="0x409">
+ Extended
+ </namerecord>
+ <namerecord nameID="266" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ <namerecord nameID="267" platformID="3" platEncID="1" langID="0x409">
+ Light
+ </namerecord>
+ <namerecord nameID="268" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="269" platformID="3" platEncID="1" langID="0x409">
+ Medium
+ </namerecord>
+ <namerecord nameID="270" platformID="3" platEncID="1" langID="0x409">
+ Semibold
+ </namerecord>
+ <namerecord nameID="271" platformID="3" platEncID="1" langID="0x409">
+ Bold
+ </namerecord>
+ <namerecord nameID="272" platformID="3" platEncID="1" langID="0x409">
+ Black
+ </namerecord>
+ <namerecord nameID="273" platformID="3" platEncID="1" langID="0x409">
+ Italic
+ </namerecord>
+ <namerecord nameID="274" platformID="3" platEncID="1" langID="0x409">
+ Roman
+ </namerecord>
+ <namerecord nameID="275" platformID="3" platEncID="1" langID="0x409">
+ Caption
+ </namerecord>
+ </name>
+
+ <STAT>
+ <Version value="0x00010002"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=4 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="opsz"/>
+ <AxisNameID value="257"/> <!-- Optical Size -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ <Axis index="1">
+ <AxisTag value="wdth"/>
+ <AxisNameID value="261"/> <!-- Width -->
+ <AxisOrdering value="1"/>
+ </Axis>
+ <Axis index="2">
+ <AxisTag value="wght"/>
+ <AxisNameID value="266"/> <!-- Weight -->
+ <AxisOrdering value="2"/>
+ </Axis>
+ <Axis index="3">
+ <AxisTag value="ital"/>
+ <AxisNameID value="273"/> <!-- Italic -->
+ <AxisOrdering value="3"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=15 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="4">
+ <!-- AxisCount=2 -->
+ <Flags value="0"/>
+ <ValueNameID value="275"/> <!-- Caption -->
+ <AxisValueRecord index="0">
+ <AxisIndex value="0"/>
+ <Value value="8.0"/>
+ </AxisValueRecord>
+ <AxisValueRecord index="1">
+ <AxisIndex value="1"/>
+ <Value value="400.0"/>
+ </AxisValueRecord>
+ </AxisValue>
+ <AxisValue index="1" Format="2">
+ <AxisIndex value="0"/>
+ <Flags value="3"/> <!-- OlderSiblingFontAttribute ElidableAxisValueName -->
+ <ValueNameID value="258"/> <!-- Text -->
+ <NominalValue value="11.0"/>
+ <RangeMinValue value="9.0"/>
+ <RangeMaxValue value="12.0"/>
+ </AxisValue>
+ <AxisValue index="2" Format="2">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="259"/> <!-- Subhead -->
+ <NominalValue value="16.7"/>
+ <RangeMinValue value="12.0"/>
+ <RangeMaxValue value="24.0"/>
+ </AxisValue>
+ <AxisValue index="3" Format="2">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="260"/> <!-- Display -->
+ <NominalValue value="72.0"/>
+ <RangeMinValue value="24.0"/>
+ <RangeMaxValue value="72.0"/>
+ </AxisValue>
+ <AxisValue index="4" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="262"/> <!-- Condensed -->
+ <NominalValue value="80.0"/>
+ <RangeMinValue value="80.0"/>
+ <RangeMaxValue value="89.0"/>
+ </AxisValue>
+ <AxisValue index="5" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="263"/> <!-- Semicondensed -->
+ <NominalValue value="90.0"/>
+ <RangeMinValue value="90.0"/>
+ <RangeMaxValue value="96.0"/>
+ </AxisValue>
+ <AxisValue index="6" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="264"/> <!-- Normal -->
+ <NominalValue value="100.0"/>
+ <RangeMinValue value="97.0"/>
+ <RangeMaxValue value="101.0"/>
+ </AxisValue>
+ <AxisValue index="7" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="265"/> <!-- Extended -->
+ <NominalValue value="125.0"/>
+ <RangeMinValue value="102.0"/>
+ <RangeMaxValue value="125.0"/>
+ </AxisValue>
+ <AxisValue index="8" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="267"/> <!-- Light -->
+ <NominalValue value="300.0"/>
+ <RangeMinValue value="300.0"/>
+ <RangeMaxValue value="349.0"/>
+ </AxisValue>
+ <AxisValue index="9" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="268"/> <!-- Regular -->
+ <NominalValue value="400.0"/>
+ <RangeMinValue value="350.0"/>
+ <RangeMaxValue value="449.0"/>
+ </AxisValue>
+ <AxisValue index="10" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="269"/> <!-- Medium -->
+ <NominalValue value="500.0"/>
+ <RangeMinValue value="450.0"/>
+ <RangeMaxValue value="549.0"/>
+ </AxisValue>
+ <AxisValue index="11" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="270"/> <!-- Semibold -->
+ <NominalValue value="600.0"/>
+ <RangeMinValue value="550.0"/>
+ <RangeMaxValue value="649.0"/>
+ </AxisValue>
+ <AxisValue index="12" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="271"/> <!-- Bold -->
+ <NominalValue value="700.0"/>
+ <RangeMinValue value="650.0"/>
+ <RangeMaxValue value="749.0"/>
+ </AxisValue>
+ <AxisValue index="13" Format="2">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="272"/> <!-- Black -->
+ <NominalValue value="900.0"/>
+ <RangeMinValue value="750.0"/>
+ <RangeMaxValue value="900.0"/>
+ </AxisValue>
+ <AxisValue index="14" Format="1">
+ <AxisIndex value="3"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="274"/> <!-- Roman -->
+ <Value value="0.0"/>
+ </AxisValue>
+ </AxisValueArray>
+ <ElidedFallbackNameID value="256"/> <!-- Roman -->
+ </STAT>
+
+</ttFont>
diff --git a/Tests/feaLib/data/SubstSubtable.fea b/Tests/feaLib/data/SubstSubtable.fea
new file mode 100644
index 00000000..b6e959a2
--- /dev/null
+++ b/Tests/feaLib/data/SubstSubtable.fea
@@ -0,0 +1,9 @@
+feature test {
+ sub G' by G.swash;
+ subtable;
+ sub H' by H.swash;
+ subtable;
+ sub G' by g;
+ subtable;
+ sub H' by H.swash;
+} test;
diff --git a/Tests/feaLib/data/SubstSubtable.ttx b/Tests/feaLib/data/SubstSubtable.ttx
new file mode 100644
index 00000000..8718f46f
--- /dev/null
+++ b/Tests/feaLib/data/SubstSubtable.ttx
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=5 -->
+ <Lookup index="0">
+ <LookupType value="5"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=4 -->
+ <ContextSubst index="0" Format="3">
+ <!-- GlyphCount=1 -->
+ <!-- SubstCount=1 -->
+ <Coverage index="0">
+ <Glyph value="G"/>
+ </Coverage>
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </SubstLookupRecord>
+ </ContextSubst>
+ <ContextSubst index="1" Format="3">
+ <!-- GlyphCount=1 -->
+ <!-- SubstCount=1 -->
+ <Coverage index="0">
+ <Glyph value="H"/>
+ </Coverage>
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="2"/>
+ </SubstLookupRecord>
+ </ContextSubst>
+ <ContextSubst index="2" Format="3">
+ <!-- GlyphCount=1 -->
+ <!-- SubstCount=1 -->
+ <Coverage index="0">
+ <Glyph value="G"/>
+ </Coverage>
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="3"/>
+ </SubstLookupRecord>
+ </ContextSubst>
+ <ContextSubst index="3" Format="3">
+ <!-- GlyphCount=1 -->
+ <!-- SubstCount=1 -->
+ <Coverage index="0">
+ <Glyph value="H"/>
+ </Coverage>
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="4"/>
+ </SubstLookupRecord>
+ </ContextSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="G" out="G.swash"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="H" out="H.swash"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="3">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="G" out="g"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="4">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="H" out="H.swash"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx b/Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx
index ce76370e..98f7aa9e 100644
--- a/Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx
+++ b/Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx
@@ -32,44 +32,39 @@
<Lookup index="0">
<LookupType value="8"/>
<LookupFlag value="0"/>
- <!-- SubTableCount=2 -->
- <ChainContextPos index="0" Format="3">
- <!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0">
- <Glyph value="A"/>
- </BacktrackCoverage>
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
- <Glyph value="G"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0">
- <Glyph value="A"/>
- </LookAheadCoverage>
- <!-- PosCount=1 -->
- <PosLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="1"/>
- </PosLookupRecord>
- </ChainContextPos>
- <ChainContextPos index="1" Format="3">
- <!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0">
- <Glyph value="B"/>
- </BacktrackCoverage>
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
+ <!-- SubTableCount=1 -->
+ <ChainContextPos index="0" Format="1">
+ <Coverage>
<Glyph value="G"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0">
- <Glyph value="B"/>
- </LookAheadCoverage>
- <!-- PosCount=1 -->
- <PosLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="1"/>
- </PosLookupRecord>
+ </Coverage>
+ <!-- ChainPosRuleSetCount=1 -->
+ <ChainPosRuleSet index="0">
+ <!-- ChainPosRuleCount=2 -->
+ <ChainPosRule index="0">
+ <!-- BacktrackGlyphCount=1 -->
+ <Backtrack index="0" value="A"/>
+ <!-- InputGlyphCount=1 -->
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAhead index="0" value="A"/>
+ <!-- PosCount=1 -->
+ <PosLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </PosLookupRecord>
+ </ChainPosRule>
+ <ChainPosRule index="1">
+ <!-- BacktrackGlyphCount=1 -->
+ <Backtrack index="0" value="B"/>
+ <!-- InputGlyphCount=1 -->
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAhead index="0" value="B"/>
+ <!-- PosCount=1 -->
+ <PosLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </PosLookupRecord>
+ </ChainPosRule>
+ </ChainPosRuleSet>
</ChainContextPos>
</Lookup>
<Lookup index="1">
diff --git a/Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.ttx b/Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.ttx
index e1cb5d61..973cb4f6 100644
--- a/Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.ttx
+++ b/Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.ttx
@@ -32,44 +32,39 @@
<Lookup index="0">
<LookupType value="8"/>
<LookupFlag value="0"/>
- <!-- SubTableCount=2 -->
- <ChainContextPos index="0" Format="3">
- <!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0">
- <Glyph value="A"/>
- </BacktrackCoverage>
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
- <Glyph value="G"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0">
- <Glyph value="A"/>
- </LookAheadCoverage>
- <!-- PosCount=1 -->
- <PosLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="1"/>
- </PosLookupRecord>
- </ChainContextPos>
- <ChainContextPos index="1" Format="3">
- <!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0">
- <Glyph value="B"/>
- </BacktrackCoverage>
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
+ <!-- SubTableCount=1 -->
+ <ChainContextPos index="0" Format="1">
+ <Coverage>
<Glyph value="G"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0">
- <Glyph value="B"/>
- </LookAheadCoverage>
- <!-- PosCount=1 -->
- <PosLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="1"/>
- </PosLookupRecord>
+ </Coverage>
+ <!-- ChainPosRuleSetCount=1 -->
+ <ChainPosRuleSet index="0">
+ <!-- ChainPosRuleCount=2 -->
+ <ChainPosRule index="0">
+ <!-- BacktrackGlyphCount=1 -->
+ <Backtrack index="0" value="A"/>
+ <!-- InputGlyphCount=1 -->
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAhead index="0" value="A"/>
+ <!-- PosCount=1 -->
+ <PosLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </PosLookupRecord>
+ </ChainPosRule>
+ <ChainPosRule index="1">
+ <!-- BacktrackGlyphCount=1 -->
+ <Backtrack index="0" value="B"/>
+ <!-- InputGlyphCount=1 -->
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAhead index="0" value="B"/>
+ <!-- PosCount=1 -->
+ <PosLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </PosLookupRecord>
+ </ChainPosRule>
+ </ChainPosRuleSet>
</ChainContextPos>
</Lookup>
<Lookup index="1">
diff --git a/Tests/feaLib/data/aalt_chain_contextual_subst.fea b/Tests/feaLib/data/aalt_chain_contextual_subst.fea
new file mode 100644
index 00000000..677c2304
--- /dev/null
+++ b/Tests/feaLib/data/aalt_chain_contextual_subst.fea
@@ -0,0 +1,20 @@
+# https://github.com/googlefonts/fontmake/issues/648
+
+lookup CNTXT_LIGS {
+ sub f i by f_i;
+ sub c t by c_t;
+} CNTXT_LIGS;
+
+lookup CNTXT_SUB {
+ sub n by n.end;
+ sub s by s.end;
+} CNTXT_SUB;
+
+feature calt {
+ sub [a e i o u] f' lookup CNTXT_LIGS i' n' lookup CNTXT_SUB;
+ sub [a e i o u] c' lookup CNTXT_LIGS t' s' lookup CNTXT_SUB;
+} calt;
+
+feature aalt {
+ feature calt;
+} aalt;
diff --git a/Tests/feaLib/data/aalt_chain_contextual_subst.ttx b/Tests/feaLib/data/aalt_chain_contextual_subst.ttx
new file mode 100644
index 00000000..256a9c72
--- /dev/null
+++ b/Tests/feaLib/data/aalt_chain_contextual_subst.ttx
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="0"/>
+ <FeatureIndex index="1" value="1"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=2 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="aalt"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="calt"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="3"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=4 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="n" out="n.end"/>
+ <Substitution in="s" out="s.end"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="4"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <LigatureSubst index="0">
+ <LigatureSet glyph="c">
+ <Ligature components="t" glyph="c_t"/>
+ </LigatureSet>
+ <LigatureSet glyph="f">
+ <Ligature components="i" glyph="f_i"/>
+ </LigatureSet>
+ </LigatureSubst>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="n" out="n.end"/>
+ <Substitution in="s" out="s.end"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="3">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=2 -->
+ <ChainContextSubst index="0" Format="3">
+ <!-- BacktrackGlyphCount=1 -->
+ <BacktrackCoverage index="0">
+ <Glyph value="a"/>
+ <Glyph value="e"/>
+ <Glyph value="i"/>
+ <Glyph value="o"/>
+ <Glyph value="u"/>
+ </BacktrackCoverage>
+ <!-- InputGlyphCount=3 -->
+ <InputCoverage index="0">
+ <Glyph value="f"/>
+ </InputCoverage>
+ <InputCoverage index="1">
+ <Glyph value="i"/>
+ </InputCoverage>
+ <InputCoverage index="2">
+ <Glyph value="n"/>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=2 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="1">
+ <SequenceIndex value="2"/>
+ <LookupListIndex value="2"/>
+ </SubstLookupRecord>
+ </ChainContextSubst>
+ <ChainContextSubst index="1" Format="3">
+ <!-- BacktrackGlyphCount=1 -->
+ <BacktrackCoverage index="0">
+ <Glyph value="a"/>
+ <Glyph value="e"/>
+ <Glyph value="i"/>
+ <Glyph value="o"/>
+ <Glyph value="u"/>
+ </BacktrackCoverage>
+ <!-- InputGlyphCount=3 -->
+ <InputCoverage index="0">
+ <Glyph value="c"/>
+ </InputCoverage>
+ <InputCoverage index="1">
+ <Glyph value="t"/>
+ </InputCoverage>
+ <InputCoverage index="2">
+ <Glyph value="s"/>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=2 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="1">
+ <SequenceIndex value="2"/>
+ <LookupListIndex value="2"/>
+ </SubstLookupRecord>
+ </ChainContextSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/feaLib/data/bug453.fea b/Tests/feaLib/data/bug453.fea
index 486632ee..ed0e6f94 100644
--- a/Tests/feaLib/data/bug453.fea
+++ b/Tests/feaLib/data/bug453.fea
@@ -2,10 +2,12 @@
feature mark {
lookup mark1 {
markClass [acute] <anchor 150 -10> @TOP_MARKS;
- pos base [e] <anchor 250 450> mark @TOP_MARKS;
+ pos base [e]
+ <anchor 250 450> mark @TOP_MARKS;
} mark1;
lookup mark2 {
markClass [acute] <anchor 150 -20> @TOP_MARKS_2;
- pos base [e] <anchor 250 450> mark @TOP_MARKS_2;
+ pos base [e]
+ <anchor 250 450> mark @TOP_MARKS_2;
} mark2;
} mark;
diff --git a/Tests/feaLib/data/bug506.ttx b/Tests/feaLib/data/bug506.ttx
index 9aff9135..4daba45b 100644
--- a/Tests/feaLib/data/bug506.ttx
+++ b/Tests/feaLib/data/bug506.ttx
@@ -30,25 +30,23 @@
<LookupList>
<!-- LookupCount=2 -->
<Lookup index="0">
- <LookupType value="6"/>
+ <LookupType value="5"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ChainContextSubst index="0" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=2 -->
- <InputCoverage index="0">
+ <ContextSubst index="0" Format="3">
+ <!-- GlyphCount=2 -->
+ <!-- SubstCount=1 -->
+ <Coverage index="0">
<Glyph value="f"/>
- </InputCoverage>
- <InputCoverage index="1">
+ </Coverage>
+ <Coverage index="1">
<Glyph value="i"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- SubstCount=1 -->
+ </Coverage>
<SubstLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="1"/>
</SubstLookupRecord>
- </ChainContextSubst>
+ </ContextSubst>
</Lookup>
<Lookup index="1">
<LookupType value="4"/>
diff --git a/Tests/feaLib/data/bug509.ttx b/Tests/feaLib/data/bug509.ttx
index e5a36afd..52fba201 100644
--- a/Tests/feaLib/data/bug509.ttx
+++ b/Tests/feaLib/data/bug509.ttx
@@ -30,33 +30,29 @@
<LookupList>
<!-- LookupCount=2 -->
<Lookup index="0">
- <LookupType value="6"/>
+ <LookupType value="5"/>
<LookupFlag value="0"/>
<!-- SubTableCount=2 -->
- <ChainContextSubst index="0" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
- <Glyph value="A"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
+ <ContextSubst index="0" Format="3">
+ <!-- GlyphCount=1 -->
<!-- SubstCount=0 -->
- </ChainContextSubst>
- <ChainContextSubst index="1" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
+ <Coverage index="0">
+ <Glyph value="A"/>
+ </Coverage>
+ </ContextSubst>
+ <ContextSubst index="1" Format="3">
+ <!-- GlyphCount=1 -->
+ <!-- SubstCount=1 -->
+ <Coverage index="0">
<Glyph value="A"/>
<Glyph value="A.sc"/>
<Glyph value="A.alt1"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- SubstCount=1 -->
+ </Coverage>
<SubstLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="1"/>
</SubstLookupRecord>
- </ChainContextSubst>
+ </ContextSubst>
</Lookup>
<Lookup index="1">
<LookupType value="1"/>
diff --git a/Tests/feaLib/data/bug512.ttx b/Tests/feaLib/data/bug512.ttx
index 1dfe63fe..693ebeb7 100644
--- a/Tests/feaLib/data/bug512.ttx
+++ b/Tests/feaLib/data/bug512.ttx
@@ -30,61 +30,54 @@
<LookupList>
<!-- LookupCount=3 -->
<Lookup index="0">
- <LookupType value="6"/>
+ <LookupType value="5"/>
<LookupFlag value="0"/>
- <!-- SubTableCount=4 -->
- <ChainContextSubst index="0" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
- <Glyph value="G"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- SubstCount=1 -->
- <SubstLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="1"/>
- </SubstLookupRecord>
- </ChainContextSubst>
- <ChainContextSubst index="1" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
- <Glyph value="H"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- SubstCount=1 -->
- <SubstLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="1"/>
- </SubstLookupRecord>
- </ChainContextSubst>
- <ChainContextSubst index="2" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
+ <!-- SubTableCount=1 -->
+ <ContextSubst index="0" Format="1">
+ <Coverage>
<Glyph value="G"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- SubstCount=1 -->
- <SubstLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="2"/>
- </SubstLookupRecord>
- </ChainContextSubst>
- <ChainContextSubst index="3" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=1 -->
- <InputCoverage index="0">
<Glyph value="H"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- SubstCount=1 -->
- <SubstLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="2"/>
- </SubstLookupRecord>
- </ChainContextSubst>
+ </Coverage>
+ <!-- SubRuleSetCount=2 -->
+ <SubRuleSet index="0">
+ <!-- SubRuleCount=2 -->
+ <SubRule index="0">
+ <!-- GlyphCount=1 -->
+ <!-- SubstCount=1 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </SubstLookupRecord>
+ </SubRule>
+ <SubRule index="1">
+ <!-- GlyphCount=1 -->
+ <!-- SubstCount=1 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="2"/>
+ </SubstLookupRecord>
+ </SubRule>
+ </SubRuleSet>
+ <SubRuleSet index="1">
+ <!-- SubRuleCount=2 -->
+ <SubRule index="0">
+ <!-- GlyphCount=1 -->
+ <!-- SubstCount=1 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </SubstLookupRecord>
+ </SubRule>
+ <SubRule index="1">
+ <!-- GlyphCount=1 -->
+ <!-- SubstCount=1 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="2"/>
+ </SubstLookupRecord>
+ </SubRule>
+ </SubRuleSet>
+ </ContextSubst>
</Lookup>
<Lookup index="1">
<LookupType value="1"/>
diff --git a/Tests/feaLib/data/bug514.fea b/Tests/feaLib/data/bug514.fea
index 26da8655..1ef5af6a 100644
--- a/Tests/feaLib/data/bug514.fea
+++ b/Tests/feaLib/data/bug514.fea
@@ -5,7 +5,7 @@
# makeotf produces {A:-40, B:-40, C:-40} and {A:-111, B:-40} which
# is redundant. https://github.com/adobe-type-tools/afdko/issues/169
feature test {
- pos X [A-B]' -40 B' -40 A' -40 Y;
+ pos X [A - B]' -40 B' -40 A' -40 Y;
pos X A' -111 Y;
- pos X B' -40 A' -111 [A-C]' -40 Y;
+ pos X B' -40 A' -111 [A - C]' -40 Y;
} test;
diff --git a/Tests/feaLib/data/bug633.ttx b/Tests/feaLib/data/bug633.ttx
index b119ebbe..075c1777 100644
--- a/Tests/feaLib/data/bug633.ttx
+++ b/Tests/feaLib/data/bug633.ttx
@@ -52,6 +52,7 @@
<!-- Class2Count=3 -->
<Class1Record index="0">
<Class2Record index="0">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
<Value1 XAdvance="0"/>
diff --git a/Tests/feaLib/data/cid_range.fea b/Tests/feaLib/data/cid_range.fea
new file mode 100644
index 00000000..7a17aede
--- /dev/null
+++ b/Tests/feaLib/data/cid_range.fea
@@ -0,0 +1,6 @@
+# A CID range can be valid even if it is invalid as a glyph name range.
+# For example, [cid00800 - cid01001] is invalid.
+
+feature zero {
+ sub [\800 - \1001] by zero;
+} zero;
diff --git a/Tests/feaLib/data/cid_range.ttx b/Tests/feaLib/data/cid_range.ttx
new file mode 100644
index 00000000..48b502b7
--- /dev/null
+++ b/Tests/feaLib/data/cid_range.ttx
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="zero"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="cid00800" out="zero"/>
+ <Substitution in="cid00801" out="zero"/>
+ <Substitution in="cid00802" out="zero"/>
+ <Substitution in="cid00803" out="zero"/>
+ <Substitution in="cid00804" out="zero"/>
+ <Substitution in="cid00805" out="zero"/>
+ <Substitution in="cid00806" out="zero"/>
+ <Substitution in="cid00807" out="zero"/>
+ <Substitution in="cid00808" out="zero"/>
+ <Substitution in="cid00809" out="zero"/>
+ <Substitution in="cid00810" out="zero"/>
+ <Substitution in="cid00811" out="zero"/>
+ <Substitution in="cid00812" out="zero"/>
+ <Substitution in="cid00813" out="zero"/>
+ <Substitution in="cid00814" out="zero"/>
+ <Substitution in="cid00815" out="zero"/>
+ <Substitution in="cid00816" out="zero"/>
+ <Substitution in="cid00817" out="zero"/>
+ <Substitution in="cid00818" out="zero"/>
+ <Substitution in="cid00819" out="zero"/>
+ <Substitution in="cid00820" out="zero"/>
+ <Substitution in="cid00821" out="zero"/>
+ <Substitution in="cid00822" out="zero"/>
+ <Substitution in="cid00823" out="zero"/>
+ <Substitution in="cid00824" out="zero"/>
+ <Substitution in="cid00825" out="zero"/>
+ <Substitution in="cid00826" out="zero"/>
+ <Substitution in="cid00827" out="zero"/>
+ <Substitution in="cid00828" out="zero"/>
+ <Substitution in="cid00829" out="zero"/>
+ <Substitution in="cid00830" out="zero"/>
+ <Substitution in="cid00831" out="zero"/>
+ <Substitution in="cid00832" out="zero"/>
+ <Substitution in="cid00833" out="zero"/>
+ <Substitution in="cid00834" out="zero"/>
+ <Substitution in="cid00835" out="zero"/>
+ <Substitution in="cid00836" out="zero"/>
+ <Substitution in="cid00837" out="zero"/>
+ <Substitution in="cid00838" out="zero"/>
+ <Substitution in="cid00839" out="zero"/>
+ <Substitution in="cid00840" out="zero"/>
+ <Substitution in="cid00841" out="zero"/>
+ <Substitution in="cid00842" out="zero"/>
+ <Substitution in="cid00843" out="zero"/>
+ <Substitution in="cid00844" out="zero"/>
+ <Substitution in="cid00845" out="zero"/>
+ <Substitution in="cid00846" out="zero"/>
+ <Substitution in="cid00847" out="zero"/>
+ <Substitution in="cid00848" out="zero"/>
+ <Substitution in="cid00849" out="zero"/>
+ <Substitution in="cid00850" out="zero"/>
+ <Substitution in="cid00851" out="zero"/>
+ <Substitution in="cid00852" out="zero"/>
+ <Substitution in="cid00853" out="zero"/>
+ <Substitution in="cid00854" out="zero"/>
+ <Substitution in="cid00855" out="zero"/>
+ <Substitution in="cid00856" out="zero"/>
+ <Substitution in="cid00857" out="zero"/>
+ <Substitution in="cid00858" out="zero"/>
+ <Substitution in="cid00859" out="zero"/>
+ <Substitution in="cid00860" out="zero"/>
+ <Substitution in="cid00861" out="zero"/>
+ <Substitution in="cid00862" out="zero"/>
+ <Substitution in="cid00863" out="zero"/>
+ <Substitution in="cid00864" out="zero"/>
+ <Substitution in="cid00865" out="zero"/>
+ <Substitution in="cid00866" out="zero"/>
+ <Substitution in="cid00867" out="zero"/>
+ <Substitution in="cid00868" out="zero"/>
+ <Substitution in="cid00869" out="zero"/>
+ <Substitution in="cid00870" out="zero"/>
+ <Substitution in="cid00871" out="zero"/>
+ <Substitution in="cid00872" out="zero"/>
+ <Substitution in="cid00873" out="zero"/>
+ <Substitution in="cid00874" out="zero"/>
+ <Substitution in="cid00875" out="zero"/>
+ <Substitution in="cid00876" out="zero"/>
+ <Substitution in="cid00877" out="zero"/>
+ <Substitution in="cid00878" out="zero"/>
+ <Substitution in="cid00879" out="zero"/>
+ <Substitution in="cid00880" out="zero"/>
+ <Substitution in="cid00881" out="zero"/>
+ <Substitution in="cid00882" out="zero"/>
+ <Substitution in="cid00883" out="zero"/>
+ <Substitution in="cid00884" out="zero"/>
+ <Substitution in="cid00885" out="zero"/>
+ <Substitution in="cid00886" out="zero"/>
+ <Substitution in="cid00887" out="zero"/>
+ <Substitution in="cid00888" out="zero"/>
+ <Substitution in="cid00889" out="zero"/>
+ <Substitution in="cid00890" out="zero"/>
+ <Substitution in="cid00891" out="zero"/>
+ <Substitution in="cid00892" out="zero"/>
+ <Substitution in="cid00893" out="zero"/>
+ <Substitution in="cid00894" out="zero"/>
+ <Substitution in="cid00895" out="zero"/>
+ <Substitution in="cid00896" out="zero"/>
+ <Substitution in="cid00897" out="zero"/>
+ <Substitution in="cid00898" out="zero"/>
+ <Substitution in="cid00899" out="zero"/>
+ <Substitution in="cid00900" out="zero"/>
+ <Substitution in="cid00901" out="zero"/>
+ <Substitution in="cid00902" out="zero"/>
+ <Substitution in="cid00903" out="zero"/>
+ <Substitution in="cid00904" out="zero"/>
+ <Substitution in="cid00905" out="zero"/>
+ <Substitution in="cid00906" out="zero"/>
+ <Substitution in="cid00907" out="zero"/>
+ <Substitution in="cid00908" out="zero"/>
+ <Substitution in="cid00909" out="zero"/>
+ <Substitution in="cid00910" out="zero"/>
+ <Substitution in="cid00911" out="zero"/>
+ <Substitution in="cid00912" out="zero"/>
+ <Substitution in="cid00913" out="zero"/>
+ <Substitution in="cid00914" out="zero"/>
+ <Substitution in="cid00915" out="zero"/>
+ <Substitution in="cid00916" out="zero"/>
+ <Substitution in="cid00917" out="zero"/>
+ <Substitution in="cid00918" out="zero"/>
+ <Substitution in="cid00919" out="zero"/>
+ <Substitution in="cid00920" out="zero"/>
+ <Substitution in="cid00921" out="zero"/>
+ <Substitution in="cid00922" out="zero"/>
+ <Substitution in="cid00923" out="zero"/>
+ <Substitution in="cid00924" out="zero"/>
+ <Substitution in="cid00925" out="zero"/>
+ <Substitution in="cid00926" out="zero"/>
+ <Substitution in="cid00927" out="zero"/>
+ <Substitution in="cid00928" out="zero"/>
+ <Substitution in="cid00929" out="zero"/>
+ <Substitution in="cid00930" out="zero"/>
+ <Substitution in="cid00931" out="zero"/>
+ <Substitution in="cid00932" out="zero"/>
+ <Substitution in="cid00933" out="zero"/>
+ <Substitution in="cid00934" out="zero"/>
+ <Substitution in="cid00935" out="zero"/>
+ <Substitution in="cid00936" out="zero"/>
+ <Substitution in="cid00937" out="zero"/>
+ <Substitution in="cid00938" out="zero"/>
+ <Substitution in="cid00939" out="zero"/>
+ <Substitution in="cid00940" out="zero"/>
+ <Substitution in="cid00941" out="zero"/>
+ <Substitution in="cid00942" out="zero"/>
+ <Substitution in="cid00943" out="zero"/>
+ <Substitution in="cid00944" out="zero"/>
+ <Substitution in="cid00945" out="zero"/>
+ <Substitution in="cid00946" out="zero"/>
+ <Substitution in="cid00947" out="zero"/>
+ <Substitution in="cid00948" out="zero"/>
+ <Substitution in="cid00949" out="zero"/>
+ <Substitution in="cid00950" out="zero"/>
+ <Substitution in="cid00951" out="zero"/>
+ <Substitution in="cid00952" out="zero"/>
+ <Substitution in="cid00953" out="zero"/>
+ <Substitution in="cid00954" out="zero"/>
+ <Substitution in="cid00955" out="zero"/>
+ <Substitution in="cid00956" out="zero"/>
+ <Substitution in="cid00957" out="zero"/>
+ <Substitution in="cid00958" out="zero"/>
+ <Substitution in="cid00959" out="zero"/>
+ <Substitution in="cid00960" out="zero"/>
+ <Substitution in="cid00961" out="zero"/>
+ <Substitution in="cid00962" out="zero"/>
+ <Substitution in="cid00963" out="zero"/>
+ <Substitution in="cid00964" out="zero"/>
+ <Substitution in="cid00965" out="zero"/>
+ <Substitution in="cid00966" out="zero"/>
+ <Substitution in="cid00967" out="zero"/>
+ <Substitution in="cid00968" out="zero"/>
+ <Substitution in="cid00969" out="zero"/>
+ <Substitution in="cid00970" out="zero"/>
+ <Substitution in="cid00971" out="zero"/>
+ <Substitution in="cid00972" out="zero"/>
+ <Substitution in="cid00973" out="zero"/>
+ <Substitution in="cid00974" out="zero"/>
+ <Substitution in="cid00975" out="zero"/>
+ <Substitution in="cid00976" out="zero"/>
+ <Substitution in="cid00977" out="zero"/>
+ <Substitution in="cid00978" out="zero"/>
+ <Substitution in="cid00979" out="zero"/>
+ <Substitution in="cid00980" out="zero"/>
+ <Substitution in="cid00981" out="zero"/>
+ <Substitution in="cid00982" out="zero"/>
+ <Substitution in="cid00983" out="zero"/>
+ <Substitution in="cid00984" out="zero"/>
+ <Substitution in="cid00985" out="zero"/>
+ <Substitution in="cid00986" out="zero"/>
+ <Substitution in="cid00987" out="zero"/>
+ <Substitution in="cid00988" out="zero"/>
+ <Substitution in="cid00989" out="zero"/>
+ <Substitution in="cid00990" out="zero"/>
+ <Substitution in="cid00991" out="zero"/>
+ <Substitution in="cid00992" out="zero"/>
+ <Substitution in="cid00993" out="zero"/>
+ <Substitution in="cid00994" out="zero"/>
+ <Substitution in="cid00995" out="zero"/>
+ <Substitution in="cid00996" out="zero"/>
+ <Substitution in="cid00997" out="zero"/>
+ <Substitution in="cid00998" out="zero"/>
+ <Substitution in="cid00999" out="zero"/>
+ <Substitution in="cid01000" out="zero"/>
+ <Substitution in="cid01001" out="zero"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/feaLib/data/delete_glyph.fea b/Tests/feaLib/data/delete_glyph.fea
new file mode 100644
index 00000000..36e0f0f9
--- /dev/null
+++ b/Tests/feaLib/data/delete_glyph.fea
@@ -0,0 +1,3 @@
+feature test {
+ sub a by NULL;
+} test;
diff --git a/Tests/feaLib/data/delete_glyph.ttx b/Tests/feaLib/data/delete_glyph.ttx
new file mode 100644
index 00000000..777f6e36
--- /dev/null
+++ b/Tests/feaLib/data/delete_glyph.ttx
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MultipleSubst index="0">
+ <Substitution in="a" out=""/>
+ </MultipleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/feaLib/data/include/test.fea b/Tests/feaLib/data/include/test.fea
new file mode 100644
index 00000000..5eb5f68f
--- /dev/null
+++ b/Tests/feaLib/data/include/test.fea
@@ -0,0 +1 @@
+include(../test.fea) \ No newline at end of file
diff --git a/Tests/feaLib/data/include/test.ufo/features.fea b/Tests/feaLib/data/include/test.ufo/features.fea
new file mode 100644
index 00000000..41ccce3c
--- /dev/null
+++ b/Tests/feaLib/data/include/test.ufo/features.fea
@@ -0,0 +1 @@
+include(test.fea)
diff --git a/Tests/feaLib/data/language_required.fea b/Tests/feaLib/data/language_required.fea
index 4005a781..687c48a1 100644
--- a/Tests/feaLib/data/language_required.fea
+++ b/Tests/feaLib/data/language_required.fea
@@ -18,5 +18,5 @@ feature liga {
} liga;
feature scmp {
- sub [a-z] by [A.sc-Z.sc];
+ sub [a - z] by [A.sc - Z.sc];
} scmp;
diff --git a/Tests/feaLib/data/lookup-debug.ttx b/Tests/feaLib/data/lookup-debug.ttx
new file mode 100644
index 00000000..f8696179
--- /dev/null
+++ b/Tests/feaLib/data/lookup-debug.ttx
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=4 -->
+ <FeatureIndex index="0" value="0"/>
+ <FeatureIndex index="1" value="1"/>
+ <FeatureIndex index="2" value="2"/>
+ <FeatureIndex index="3" value="3"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=4 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="tst1"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="tst2"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="2">
+ <FeatureTag value="tst3"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="3">
+ <FeatureTag value="tst4"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <!-- SomeLookup: __PATH__:4:5 in tst2 (DFLT/dflt) -->
+ <Lookup index="0">
+ <LookupType value="4"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <LigatureSubst index="0">
+ <LigatureSet glyph="f">
+ <Ligature components="f,i" glyph="f_f_i"/>
+ <Ligature components="i" glyph="f_i"/>
+ </LigatureSet>
+ </LigatureSubst>
+ </Lookup>
+ <!-- EmbeddedLookup: __PATH__:18:9 in tst4 (DFLT/dflt) -->
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="A" out="A.sc"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/feaLib/data/lookupflag.fea b/Tests/feaLib/data/lookupflag.fea
index 651dcd03..0210ab42 100644
--- a/Tests/feaLib/data/lookupflag.fea
+++ b/Tests/feaLib/data/lookupflag.fea
@@ -95,3 +95,65 @@ feature test {
lookup M;
lookup N;
} test;
+
+feature test {
+ lookupflag IgnoreMarks;
+ lookup O {
+ pos one 1;
+ } O;
+ lookup P {
+ pos one 1;
+ } P;
+} test;
+
+feature test {
+ lookup Q {
+ pos one 1;
+ } Q;
+ lookup R {
+ pos one 1;
+ } R;
+} test;
+
+feature test {
+ lookup S {
+ lookupflag IgnoreMarks;
+ pos one 1;
+ } S;
+ lookup T {
+ pos one 1;
+ } T;
+} test;
+
+feature test {
+ lookup U {
+ pos one 1;
+ } U;
+ lookup V {
+ lookupflag IgnoreMarks;
+ pos one 1;
+ } V;
+} test;
+
+feature test {
+ lookup W {
+ lookupflag IgnoreMarks;
+ script latn;
+ pos one 1;
+ } W;
+ lookup X {
+ lookupflag IgnoreMarks;
+ script latn;
+ pos two 2;
+ } X;
+} test;
+
+lookup Y {
+ lookupflag UseMarkFilteringSet [acute grave macron];
+ pos Y 1;
+} Y;
+
+lookup Z {
+ lookupflag MarkAttachmentType [acute grave macron];
+ pos Z 1;
+} Z;
diff --git a/Tests/feaLib/data/lookupflag.ttx b/Tests/feaLib/data/lookupflag.ttx
index bb05b9a1..16ea751f 100644
--- a/Tests/feaLib/data/lookupflag.ttx
+++ b/Tests/feaLib/data/lookupflag.ttx
@@ -43,7 +43,7 @@
<GPOS>
<Version value="0x00010000"/>
<ScriptList>
- <!-- ScriptCount=1 -->
+ <!-- ScriptCount=2 -->
<ScriptRecord index="0">
<ScriptTag value="DFLT"/>
<Script>
@@ -55,13 +55,24 @@
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
+ <ScriptRecord index="1">
+ <ScriptTag value="latn"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="1"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
</ScriptList>
<FeatureList>
- <!-- FeatureCount=1 -->
+ <!-- FeatureCount=2 -->
<FeatureRecord index="0">
<FeatureTag value="test"/>
<Feature>
- <!-- LookupCount=14 -->
+ <!-- LookupCount=22 -->
<LookupListIndex index="0" value="0"/>
<LookupListIndex index="1" value="1"/>
<LookupListIndex index="2" value="2"/>
@@ -76,14 +87,30 @@
<LookupListIndex index="11" value="11"/>
<LookupListIndex index="12" value="12"/>
<LookupListIndex index="13" value="13"/>
+ <LookupListIndex index="14" value="14"/>
+ <LookupListIndex index="15" value="15"/>
+ <LookupListIndex index="16" value="16"/>
+ <LookupListIndex index="17" value="17"/>
+ <LookupListIndex index="18" value="18"/>
+ <LookupListIndex index="19" value="19"/>
+ <LookupListIndex index="20" value="20"/>
+ <LookupListIndex index="21" value="21"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=2 -->
+ <LookupListIndex index="0" value="22"/>
+ <LookupListIndex index="1" value="23"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
- <!-- LookupCount=14 -->
+ <!-- LookupCount=26 -->
<Lookup index="0">
<LookupType value="1"/>
- <LookupFlag value="1"/>
+ <LookupFlag value="1"/><!-- rightToLeft -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -95,7 +122,7 @@
</Lookup>
<Lookup index="1">
<LookupType value="1"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -107,7 +134,7 @@
</Lookup>
<Lookup index="2">
<LookupType value="1"/>
- <LookupFlag value="4"/>
+ <LookupFlag value="4"/><!-- ignoreLigatures -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -119,7 +146,7 @@
</Lookup>
<Lookup index="3">
<LookupType value="1"/>
- <LookupFlag value="7"/>
+ <LookupFlag value="7"/><!-- rightToLeft ignoreBaseGlyphs ignoreLigatures -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -131,7 +158,7 @@
</Lookup>
<Lookup index="4">
<LookupType value="1"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -143,7 +170,7 @@
</Lookup>
<Lookup index="5">
<LookupType value="1"/>
- <LookupFlag value="256"/>
+ <LookupFlag value="256"/><!-- markAttachmentType[1] -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -155,7 +182,7 @@
</Lookup>
<Lookup index="6">
<LookupType value="1"/>
- <LookupFlag value="512"/>
+ <LookupFlag value="512"/><!-- markAttachmentType[2] -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -167,7 +194,7 @@
</Lookup>
<Lookup index="7">
<LookupType value="1"/>
- <LookupFlag value="260"/>
+ <LookupFlag value="260"/><!-- ignoreLigatures markAttachmentType[1] -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -179,7 +206,7 @@
</Lookup>
<Lookup index="8">
<LookupType value="1"/>
- <LookupFlag value="16"/>
+ <LookupFlag value="16"/><!-- useMarkFilteringSet -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -192,7 +219,7 @@
</Lookup>
<Lookup index="9">
<LookupType value="1"/>
- <LookupFlag value="16"/>
+ <LookupFlag value="16"/><!-- useMarkFilteringSet -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -205,7 +232,7 @@
</Lookup>
<Lookup index="10">
<LookupType value="1"/>
- <LookupFlag value="20"/>
+ <LookupFlag value="20"/><!-- ignoreLigatures useMarkFilteringSet -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -230,7 +257,7 @@
</Lookup>
<Lookup index="12">
<LookupType value="1"/>
- <LookupFlag value="16"/>
+ <LookupFlag value="16"/><!-- useMarkFilteringSet -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -243,7 +270,7 @@
</Lookup>
<Lookup index="13">
<LookupType value="1"/>
- <LookupFlag value="768"/>
+ <LookupFlag value="768"/><!-- markAttachmentType[3] -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
<Coverage>
@@ -253,6 +280,151 @@
<Value XAdvance="1"/>
</SinglePos>
</Lookup>
+ <Lookup index="14">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="15">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="16">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="17">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="18">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="19">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="20">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="21">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="22">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="23">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="two"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="2"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="24">
+ <LookupType value="1"/>
+ <LookupFlag value="16"/><!-- useMarkFilteringSet -->
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="Y"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ <MarkFilteringSet value="0"/>
+ </Lookup>
+ <Lookup index="25">
+ <LookupType value="1"/>
+ <LookupFlag value="256"/><!-- markAttachmentType[1] -->
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="Z"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
</LookupList>
</GPOS>
diff --git a/Tests/feaLib/data/size2.ttx b/Tests/feaLib/data/size2.ttx
index a822af36..1a12ddfc 100644
--- a/Tests/feaLib/data/size2.ttx
+++ b/Tests/feaLib/data/size2.ttx
@@ -26,8 +26,8 @@
<DesignSize value="10.0"/>
<SubfamilyID value="0"/>
<SubfamilyNameID value="0"/>
- <RangeStart value="0"/>
- <RangeEnd value="0"/>
+ <RangeStart value="0.0"/>
+ <RangeEnd value="0.0"/>
</FeatureParamsSize>
<!-- LookupCount=0 -->
</Feature>
diff --git a/Tests/feaLib/data/spec4h1.fea b/Tests/feaLib/data/spec4h1.fea
index b43e13b0..a3d24946 100644
--- a/Tests/feaLib/data/spec4h1.fea
+++ b/Tests/feaLib/data/spec4h1.fea
@@ -8,7 +8,7 @@ languagesystem latn TRK;
languagesystem cyrl dflt;
feature smcp {
- sub [a-z] by [A.sc-Z.sc];
+ sub [a - z] by [A.sc - Z.sc];
# Since all the rules in this feature are of the same type, they
# will be grouped in a single lookup. Since no script or language
diff --git a/Tests/feaLib/data/spec5f_ii_2.fea b/Tests/feaLib/data/spec5f_ii_2.fea
index b20a74c3..916f797a 100644
--- a/Tests/feaLib/data/spec5f_ii_2.fea
+++ b/Tests/feaLib/data/spec5f_ii_2.fea
@@ -3,7 +3,7 @@
# http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html
feature test {
- @LETTER = [a-z];
+ @LETTER = [a - z];
ignore sub @LETTER f' i';
sub f' i' by f_i.begin;
} test;
diff --git a/Tests/feaLib/data/spec5f_ii_3.fea b/Tests/feaLib/data/spec5f_ii_3.fea
index 5fd19916..af06770d 100644
--- a/Tests/feaLib/data/spec5f_ii_3.fea
+++ b/Tests/feaLib/data/spec5f_ii_3.fea
@@ -3,7 +3,7 @@
# http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html
feature test {
- @LETTER = [a-z];
+ @LETTER = [a - z];
ignore sub @LETTER a' n' d', a' n' d' @LETTER;
sub a' n' d' by a_n_d;
} test;
diff --git a/Tests/feaLib/data/spec5f_ii_3.ttx b/Tests/feaLib/data/spec5f_ii_3.ttx
index a550f0b6..a94efcea 100644
--- a/Tests/feaLib/data/spec5f_ii_3.ttx
+++ b/Tests/feaLib/data/spec5f_ii_3.ttx
@@ -32,111 +32,115 @@
<Lookup index="0">
<LookupType value="6"/>
<LookupFlag value="0"/>
- <!-- SubTableCount=3 -->
- <ChainContextSubst index="0" Format="3">
- <!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0">
- <Glyph value="a"/>
- <Glyph value="b"/>
- <Glyph value="c"/>
- <Glyph value="d"/>
- <Glyph value="e"/>
- <Glyph value="f"/>
- <Glyph value="g"/>
- <Glyph value="h"/>
- <Glyph value="i"/>
- <Glyph value="j"/>
- <Glyph value="k"/>
- <Glyph value="l"/>
- <Glyph value="m"/>
- <Glyph value="n"/>
- <Glyph value="o"/>
- <Glyph value="p"/>
- <Glyph value="q"/>
- <Glyph value="r"/>
- <Glyph value="s"/>
- <Glyph value="t"/>
- <Glyph value="u"/>
- <Glyph value="v"/>
- <Glyph value="w"/>
- <Glyph value="x"/>
- <Glyph value="y"/>
- <Glyph value="z"/>
- </BacktrackCoverage>
- <!-- InputGlyphCount=3 -->
- <InputCoverage index="0">
- <Glyph value="a"/>
- </InputCoverage>
- <InputCoverage index="1">
- <Glyph value="n"/>
- </InputCoverage>
- <InputCoverage index="2">
- <Glyph value="d"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- SubstCount=0 -->
- </ChainContextSubst>
- <ChainContextSubst index="1" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=3 -->
- <InputCoverage index="0">
- <Glyph value="a"/>
- </InputCoverage>
- <InputCoverage index="1">
- <Glyph value="n"/>
- </InputCoverage>
- <InputCoverage index="2">
- <Glyph value="d"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0">
- <Glyph value="a"/>
- <Glyph value="b"/>
- <Glyph value="c"/>
- <Glyph value="d"/>
- <Glyph value="e"/>
- <Glyph value="f"/>
- <Glyph value="g"/>
- <Glyph value="h"/>
- <Glyph value="i"/>
- <Glyph value="j"/>
- <Glyph value="k"/>
- <Glyph value="l"/>
- <Glyph value="m"/>
- <Glyph value="n"/>
- <Glyph value="o"/>
- <Glyph value="p"/>
- <Glyph value="q"/>
- <Glyph value="r"/>
- <Glyph value="s"/>
- <Glyph value="t"/>
- <Glyph value="u"/>
- <Glyph value="v"/>
- <Glyph value="w"/>
- <Glyph value="x"/>
- <Glyph value="y"/>
- <Glyph value="z"/>
- </LookAheadCoverage>
- <!-- SubstCount=0 -->
- </ChainContextSubst>
- <ChainContextSubst index="2" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=3 -->
- <InputCoverage index="0">
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="2">
+ <Coverage>
<Glyph value="a"/>
- </InputCoverage>
- <InputCoverage index="1">
- <Glyph value="n"/>
- </InputCoverage>
- <InputCoverage index="2">
- <Glyph value="d"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- SubstCount=1 -->
- <SubstLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="1"/>
- </SubstLookupRecord>
+ </Coverage>
+ <BacktrackClassDef>
+ <ClassDef glyph="a" class="1"/>
+ <ClassDef glyph="b" class="1"/>
+ <ClassDef glyph="c" class="1"/>
+ <ClassDef glyph="d" class="1"/>
+ <ClassDef glyph="e" class="1"/>
+ <ClassDef glyph="f" class="1"/>
+ <ClassDef glyph="g" class="1"/>
+ <ClassDef glyph="h" class="1"/>
+ <ClassDef glyph="i" class="1"/>
+ <ClassDef glyph="j" class="1"/>
+ <ClassDef glyph="k" class="1"/>
+ <ClassDef glyph="l" class="1"/>
+ <ClassDef glyph="m" class="1"/>
+ <ClassDef glyph="n" class="1"/>
+ <ClassDef glyph="o" class="1"/>
+ <ClassDef glyph="p" class="1"/>
+ <ClassDef glyph="q" class="1"/>
+ <ClassDef glyph="r" class="1"/>
+ <ClassDef glyph="s" class="1"/>
+ <ClassDef glyph="t" class="1"/>
+ <ClassDef glyph="u" class="1"/>
+ <ClassDef glyph="v" class="1"/>
+ <ClassDef glyph="w" class="1"/>
+ <ClassDef glyph="x" class="1"/>
+ <ClassDef glyph="y" class="1"/>
+ <ClassDef glyph="z" class="1"/>
+ </BacktrackClassDef>
+ <InputClassDef>
+ <ClassDef glyph="a" class="3"/>
+ <ClassDef glyph="d" class="2"/>
+ <ClassDef glyph="n" class="1"/>
+ </InputClassDef>
+ <LookAheadClassDef>
+ <ClassDef glyph="a" class="1"/>
+ <ClassDef glyph="b" class="1"/>
+ <ClassDef glyph="c" class="1"/>
+ <ClassDef glyph="d" class="1"/>
+ <ClassDef glyph="e" class="1"/>
+ <ClassDef glyph="f" class="1"/>
+ <ClassDef glyph="g" class="1"/>
+ <ClassDef glyph="h" class="1"/>
+ <ClassDef glyph="i" class="1"/>
+ <ClassDef glyph="j" class="1"/>
+ <ClassDef glyph="k" class="1"/>
+ <ClassDef glyph="l" class="1"/>
+ <ClassDef glyph="m" class="1"/>
+ <ClassDef glyph="n" class="1"/>
+ <ClassDef glyph="o" class="1"/>
+ <ClassDef glyph="p" class="1"/>
+ <ClassDef glyph="q" class="1"/>
+ <ClassDef glyph="r" class="1"/>
+ <ClassDef glyph="s" class="1"/>
+ <ClassDef glyph="t" class="1"/>
+ <ClassDef glyph="u" class="1"/>
+ <ClassDef glyph="v" class="1"/>
+ <ClassDef glyph="w" class="1"/>
+ <ClassDef glyph="x" class="1"/>
+ <ClassDef glyph="y" class="1"/>
+ <ClassDef glyph="z" class="1"/>
+ </LookAheadClassDef>
+ <!-- ChainSubClassSetCount=4 -->
+ <ChainSubClassSet index="0">
+ <!-- ChainSubClassRuleCount=0 -->
+ </ChainSubClassSet>
+ <ChainSubClassSet index="1">
+ <!-- ChainSubClassRuleCount=0 -->
+ </ChainSubClassSet>
+ <ChainSubClassSet index="2">
+ <!-- ChainSubClassRuleCount=0 -->
+ </ChainSubClassSet>
+ <ChainSubClassSet index="3">
+ <!-- ChainSubClassRuleCount=3 -->
+ <ChainSubClassRule index="0">
+ <!-- BacktrackGlyphCount=1 -->
+ <Backtrack index="0" value="1"/>
+ <!-- InputGlyphCount=3 -->
+ <Input index="0" value="1"/>
+ <Input index="1" value="2"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=0 -->
+ </ChainSubClassRule>
+ <ChainSubClassRule index="1">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=3 -->
+ <Input index="0" value="1"/>
+ <Input index="1" value="2"/>
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAhead index="0" value="1"/>
+ <!-- SubstCount=0 -->
+ </ChainSubClassRule>
+ <ChainSubClassRule index="2">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=3 -->
+ <Input index="0" value="1"/>
+ <Input index="1" value="2"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=1 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="1"/>
+ </SubstLookupRecord>
+ </ChainSubClassRule>
+ </ChainSubClassSet>
</ChainContextSubst>
</Lookup>
<Lookup index="1">
diff --git a/Tests/feaLib/data/spec5f_ii_4.fea b/Tests/feaLib/data/spec5f_ii_4.fea
index 731a1f64..bc6fda43 100644
--- a/Tests/feaLib/data/spec5f_ii_4.fea
+++ b/Tests/feaLib/data/spec5f_ii_4.fea
@@ -2,13 +2,13 @@
# "Specifying exceptions to the Chain Sub rule"
# http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html
-@LETTER = [A-Z a-z];
+@LETTER = [A - Z a - z];
feature cswh {
# --- Glyph classes used in this feature:
- @BEGINNINGS = [A-N P-Z T_h m];
- @BEGINNINGS_SWASH = [A.swash-N.swash P.swash-Z.swash T_h.swash m.begin];
+ @BEGINNINGS = [A - N P - Z T_h m];
+ @BEGINNINGS_SWASH = [A.swash - N.swash P.swash - Z.swash T_h.swash m.begin];
@ENDINGS = [a e z];
@ENDINGS_SWASH = [a.end e.end z.end];
diff --git a/Tests/feaLib/data/spec5fi2.ttx b/Tests/feaLib/data/spec5fi2.ttx
index 0842cf92..6485c34b 100644
--- a/Tests/feaLib/data/spec5fi2.ttx
+++ b/Tests/feaLib/data/spec5fi2.ttx
@@ -31,7 +31,7 @@
<!-- LookupCount=2 -->
<Lookup index="0">
<LookupType value="6"/>
- <LookupFlag value="7"/>
+ <LookupFlag value="7"/><!-- rightToLeft ignoreBaseGlyphs ignoreLigatures -->
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
@@ -54,7 +54,7 @@
</Lookup>
<Lookup index="1">
<LookupType value="1"/>
- <LookupFlag value="7"/>
+ <LookupFlag value="7"/><!-- rightToLeft ignoreBaseGlyphs ignoreLigatures -->
<!-- SubTableCount=1 -->
<SingleSubst index="0">
<Substitution in="d" out="d.alt"/>
diff --git a/Tests/feaLib/data/spec5fi3.fea b/Tests/feaLib/data/spec5fi3.fea
index e47a6f8a..e44a7324 100644
--- a/Tests/feaLib/data/spec5fi3.fea
+++ b/Tests/feaLib/data/spec5fi3.fea
@@ -5,5 +5,5 @@
languagesystem latn dflt;
feature test {
- sub [A-Z] [A.sc-Z.sc]' by [a-z];
+ sub [A - Z] [A.sc - Z.sc]' by [a - z];
} test;
diff --git a/Tests/feaLib/data/spec6b_ii.ttx b/Tests/feaLib/data/spec6b_ii.ttx
index a7131ded..c7b8de81 100644
--- a/Tests/feaLib/data/spec6b_ii.ttx
+++ b/Tests/feaLib/data/spec6b_ii.ttx
@@ -91,6 +91,7 @@
<!-- Class2Count=2 -->
<Class1Record index="0">
<Class2Record index="0">
+ <Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
<Value1 XAdvance="-100"/>
diff --git a/Tests/feaLib/data/spec6d2.fea b/Tests/feaLib/data/spec6d2.fea
index ead224fe..5c2620d2 100644
--- a/Tests/feaLib/data/spec6d2.fea
+++ b/Tests/feaLib/data/spec6d2.fea
@@ -9,7 +9,11 @@ markClass [dieresis umlaut] <anchor 300 -10> @TOP_MARKS;
markClass [cedilla] <anchor 300 600> @BOTTOM_MARKS;
feature test {
- pos base [e o] <anchor 250 450> mark @TOP_MARKS <anchor 250 -12> mark @BOTTOM_MARKS;
-#test-fea2fea: pos base [a u] <anchor 265 450> mark @TOP_MARKS <anchor 250 -10> mark @BOTTOM_MARKS;
- position base [a u] <anchor 265 450> mark @TOP_MARKS <anchor 250-10> mark @BOTTOM_MARKS;
+ pos base [e o]
+ <anchor 250 450> mark @TOP_MARKS
+ <anchor 250 -12> mark @BOTTOM_MARKS;
+#test-fea2fea: pos base [a u]
+ position base [a u]
+ <anchor 265 450> mark @TOP_MARKS
+ <anchor 250 -10> mark @BOTTOM_MARKS;
} test;
diff --git a/Tests/feaLib/data/spec6e.fea b/Tests/feaLib/data/spec6e.fea
index ed956c8f..64612232 100644
--- a/Tests/feaLib/data/spec6e.fea
+++ b/Tests/feaLib/data/spec6e.fea
@@ -4,7 +4,10 @@ markClass sukun <anchor 261 488> @TOP_MARKS;
markClass kasratan <anchor 346 -98> @BOTTOM_MARKS;
feature test {
- pos ligature lam_meem_jeem <anchor 625 1800> mark @TOP_MARKS # mark above lam
- ligComponent <anchor 376 -368> mark @BOTTOM_MARKS # mark below meem
- ligComponent <anchor NULL>; # jeem has no marks
+ pos ligature lam_meem_jeem
+ <anchor 625 1800> mark @TOP_MARKS # mark above lam
+ ligComponent
+ <anchor 376 -368> mark @BOTTOM_MARKS # mark below meem
+ ligComponent
+ <anchor NULL>; # jeem has no marks
} test;
diff --git a/Tests/feaLib/data/spec6f.fea b/Tests/feaLib/data/spec6f.fea
index 8d32008c..277bdb46 100644
--- a/Tests/feaLib/data/spec6f.fea
+++ b/Tests/feaLib/data/spec6f.fea
@@ -2,5 +2,6 @@ languagesystem DFLT dflt;
feature test {
markClass damma <anchor 189 -103> @MARK_CLASS_1;
- pos mark hamza <anchor 221 301> mark @MARK_CLASS_1;
+ pos mark hamza
+ <anchor 221 301> mark @MARK_CLASS_1;
} test;
diff --git a/Tests/feaLib/data/spec6h_ii.fea b/Tests/feaLib/data/spec6h_ii.fea
index 36a1f032..690d2a35 100644
--- a/Tests/feaLib/data/spec6h_ii.fea
+++ b/Tests/feaLib/data/spec6h_ii.fea
@@ -12,8 +12,10 @@ lookup CNTXT_PAIR_POS {
} CNTXT_PAIR_POS;
lookup CNTXT_MARK_TO_BASE {
- pos base o <anchor 250 450> mark @ALL_MARKS;
- pos base c <anchor 250 450> mark @ALL_MARKS;
+ pos base o
+ <anchor 250 450> mark @ALL_MARKS;
+ pos base c
+ <anchor 250 450> mark @ALL_MARKS;
} CNTXT_MARK_TO_BASE;
feature test {
diff --git a/Tests/feaLib/data/spec6h_ii.ttx b/Tests/feaLib/data/spec6h_ii.ttx
index e8ec85f2..2f0efc6f 100644
--- a/Tests/feaLib/data/spec6h_ii.ttx
+++ b/Tests/feaLib/data/spec6h_ii.ttx
@@ -112,25 +112,23 @@
</MarkBasePos>
</Lookup>
<Lookup index="2">
- <LookupType value="8"/>
+ <LookupType value="7"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ChainContextPos index="0" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=3 -->
- <InputCoverage index="0">
+ <ContextPos index="0" Format="3">
+ <!-- GlyphCount=3 -->
+ <!-- PosCount=2 -->
+ <Coverage index="0">
<Glyph value="T"/>
- </InputCoverage>
- <InputCoverage index="1">
+ </Coverage>
+ <Coverage index="1">
<Glyph value="c"/>
<Glyph value="o"/>
- </InputCoverage>
- <InputCoverage index="2">
+ </Coverage>
+ <Coverage index="2">
<Glyph value="grave"/>
<Glyph value="acute"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- PosCount=2 -->
+ </Coverage>
<PosLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="0"/>
@@ -139,7 +137,7 @@
<SequenceIndex value="2"/>
<LookupListIndex value="1"/>
</PosLookupRecord>
- </ChainContextPos>
+ </ContextPos>
</Lookup>
</LookupList>
</GPOS>
diff --git a/Tests/feaLib/data/spec6h_iii_3d.ttx b/Tests/feaLib/data/spec6h_iii_3d.ttx
index a608f0e6..2335dd0b 100644
--- a/Tests/feaLib/data/spec6h_iii_3d.ttx
+++ b/Tests/feaLib/data/spec6h_iii_3d.ttx
@@ -30,25 +30,23 @@
<LookupList>
<!-- LookupCount=2 -->
<Lookup index="0">
- <LookupType value="8"/>
+ <LookupType value="7"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ChainContextPos index="0" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=2 -->
- <InputCoverage index="0">
+ <ContextPos index="0" Format="3">
+ <!-- GlyphCount=2 -->
+ <!-- PosCount=1 -->
+ <Coverage index="0">
<Glyph value="L"/>
- </InputCoverage>
- <InputCoverage index="1">
+ </Coverage>
+ <Coverage index="1">
<Glyph value="quoteright"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- PosCount=1 -->
+ </Coverage>
<PosLookupRecord index="0">
<SequenceIndex value="1"/>
<LookupListIndex value="1"/>
</PosLookupRecord>
- </ChainContextPos>
+ </ContextPos>
</Lookup>
<Lookup index="1">
<LookupType value="1"/>
diff --git a/Tests/feaLib/data/spec8a.fea b/Tests/feaLib/data/spec8a.fea
index b4d7dd29..40548213 100644
--- a/Tests/feaLib/data/spec8a.fea
+++ b/Tests/feaLib/data/spec8a.fea
@@ -10,7 +10,7 @@ feature aalt {
} aalt;
feature smcp {
- sub [a-c] by [A.sc-C.sc];
+ sub [a - c] by [A.sc - C.sc];
sub f i by f_i; # not considered for aalt
} smcp;
diff --git a/Tests/feaLib/data/test.fea b/Tests/feaLib/data/test.fea
new file mode 100644
index 00000000..c01ade29
--- /dev/null
+++ b/Tests/feaLib/data/test.fea
@@ -0,0 +1 @@
+# Nothing
diff --git a/Tests/feaLib/error_test.py b/Tests/feaLib/error_test.py
index 87cbecb2..2ebb3e4c 100644
--- a/Tests/feaLib/error_test.py
+++ b/Tests/feaLib/error_test.py
@@ -1,12 +1,11 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
from fontTools.feaLib.error import FeatureLibError
+from fontTools.feaLib.location import FeatureLibLocation
import unittest
class FeatureLibErrorTest(unittest.TestCase):
def test_str(self):
- err = FeatureLibError("Squeak!", ("foo.fea", 23, 42))
+ err = FeatureLibError("Squeak!", FeatureLibLocation("foo.fea", 23, 42))
self.assertEqual(str(err), "foo.fea:23:42: Squeak!")
def test_str_nolocation(self):
diff --git a/Tests/feaLib/lexer_test.py b/Tests/feaLib/lexer_test.py
index 1b28fa09..24dc5dba 100644
--- a/Tests/feaLib/lexer_test.py
+++ b/Tests/feaLib/lexer_test.py
@@ -1,8 +1,7 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes
from fontTools.feaLib.error import FeatureLibError, IncludedFeaNotFound
from fontTools.feaLib.lexer import IncludingLexer, Lexer
+from io import StringIO
import os
import shutil
import tempfile
@@ -69,8 +68,9 @@ class LexerTest(unittest.TestCase):
def test_number(self):
self.assertEqual(lex("123 -456"),
[(Lexer.NUMBER, 123), (Lexer.NUMBER, -456)])
- self.assertEqual(lex("0xCAFED00D"), [(Lexer.NUMBER, 0xCAFED00D)])
- self.assertEqual(lex("0xcafed00d"), [(Lexer.NUMBER, 0xCAFED00D)])
+ self.assertEqual(lex("0xCAFED00D"), [(Lexer.HEXADECIMAL, 0xCAFED00D)])
+ self.assertEqual(lex("0xcafed00d"), [(Lexer.HEXADECIMAL, 0xCAFED00D)])
+ self.assertEqual(lex("010"), [(Lexer.OCTAL, 0o10)])
def test_float(self):
self.assertEqual(lex("1.23 -4.5"),
@@ -108,7 +108,7 @@ class LexerTest(unittest.TestCase):
def test_newline(self):
def lines(s):
- return [loc[1] for (_, _, loc) in Lexer(s, "test.fea")]
+ return [loc.line for (_, _, loc) in Lexer(s, "test.fea")]
self.assertEqual(lines("FOO\n\nBAR\nBAZ"), [1, 3, 4]) # Unix
self.assertEqual(lines("FOO\r\rBAR\rBAZ"), [1, 3, 4]) # Macintosh
self.assertEqual(lines("FOO\r\n\r\n BAR\r\nBAZ"), [1, 3, 4]) # Windows
@@ -116,7 +116,7 @@ class LexerTest(unittest.TestCase):
def test_location(self):
def locs(s):
- return ["%s:%d:%d" % loc for (_, _, loc) in Lexer(s, "test.fea")]
+ return [str(loc) for (_, _, loc) in Lexer(s, "test.fea")]
self.assertEqual(locs("a b # Comment\n12 @x"), [
"test.fea:1:1", "test.fea:1:3", "test.fea:1:5", "test.fea:2:1",
"test.fea:2:4"
@@ -151,7 +151,7 @@ class IncludingLexerTest(unittest.TestCase):
def test_include(self):
lexer = IncludingLexer(self.getpath("include/include4.fea"))
- result = ['%s %s:%d' % (token, os.path.split(loc[0])[1], loc[1])
+ result = ['%s %s:%d' % (token, os.path.split(loc.file)[1], loc.line)
for _, token, loc in lexer]
self.assertEqual(result, [
"I4a include4.fea:1",
@@ -179,13 +179,15 @@ class IncludingLexerTest(unittest.TestCase):
def test_include_missing_file(self):
lexer = IncludingLexer(self.getpath("include/includemissingfile.fea"))
self.assertRaisesRegex(IncludedFeaNotFound,
- "includemissingfile.fea:1:8: missingfile.fea",
+ "includemissingfile.fea:1:8: The following feature file "
+ "should be included but cannot be found: "
+ "missingfile.fea",
lambda: list(lexer))
def test_featurefilepath_None(self):
- lexer = IncludingLexer(UnicodeIO("# foobar"))
+ lexer = IncludingLexer(StringIO("# foobar"))
self.assertIsNone(lexer.featurefilepath)
- files = set(loc[0] for _, _, loc in lexer)
+ files = set(loc.file for _, _, loc in lexer)
self.assertIn("<features>", files)
def test_include_absolute_path(self):
@@ -195,10 +197,10 @@ class IncludingLexerTest(unittest.TestCase):
pos A B -40;
} kern;
""", encoding="utf-8"))
- including = UnicodeIO("include(%s);" % included.name)
+ including = StringIO("include(%s);" % included.name)
try:
lexer = IncludingLexer(including)
- files = set(loc[0] for _, _, loc in lexer)
+ files = set(loc.file for _, _, loc in lexer)
self.assertIn(included.name, files)
finally:
os.remove(included.name)
@@ -223,8 +225,8 @@ class IncludingLexerTest(unittest.TestCase):
# itself have a path, because it was initialized from
# an in-memory stream, so it will use the current working
# directory to resolve relative include statements
- lexer = IncludingLexer(UnicodeIO("include(included.fea);"))
- files = set(loc[0] for _, _, loc in lexer)
+ lexer = IncludingLexer(StringIO("include(included.fea);"))
+ files = set(os.path.realpath(loc.file) for _, _, loc in lexer)
expected = os.path.realpath(included.name)
self.assertIn(expected, files)
finally:
diff --git a/Tests/feaLib/parser_test.py b/Tests/feaLib/parser_test.py
index 9343495a..de2bc3ca 100644
--- a/Tests/feaLib/parser_test.py
+++ b/Tests/feaLib/parser_test.py
@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
+from fontTools.misc.loggingTools import CapturingLogHandler
from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.parser import Parser, SymbolTable
-from fontTools.misc.py23 import *
+from io import StringIO
import warnings
import fontTools.feaLib.ast as ast
import os
@@ -41,6 +40,14 @@ GLYPHNAMES = ("""
n.sc o.sc p.sc q.sc r.sc s.sc t.sc u.sc v.sc w.sc x.sc y.sc z.sc
a.swash b.swash x.swash y.swash z.swash
foobar foo.09 foo.1234 foo.9876
+ one two five six acute grave dieresis umlaut cedilla ogonek macron
+ a_f_f_i o_f_f_i f_i f_f_i one.fitted one.oldstyle a.1 a.2 a.3 c_t
+ PRE SUF FIX BACK TRACK LOOK AHEAD ampersand ampersand.1 ampersand.2
+ cid00001 cid00002 cid00003 cid00004 cid00005 cid00006 cid00007
+ cid12345 cid78987 cid00999 cid01000 cid01001 cid00998 cid00995
+ cid00111 cid00222
+ comma endash emdash figuredash damma hamza
+ c_d d.alt n.end s.end f_f
""").split() + ["foo.%d" % i for i in range(1, 200)]
@@ -56,7 +63,7 @@ class ParserTest(unittest.TestCase):
glyphMap = {'a': 0, 'b': 1, 'c': 2}
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
- parser = Parser(UnicodeIO(), glyphMap=glyphMap)
+ parser = Parser(StringIO(), glyphMap=glyphMap)
self.assertEqual(len(w), 1)
self.assertEqual(w[-1].category, UserWarning)
@@ -65,11 +72,11 @@ class ParserTest(unittest.TestCase):
self.assertRaisesRegex(
TypeError, "mutually exclusive",
- Parser, UnicodeIO(), ("a",), glyphMap={"a": 0})
+ Parser, StringIO(), ("a",), glyphMap={"a": 0})
self.assertRaisesRegex(
TypeError, "unsupported keyword argument",
- Parser, UnicodeIO(), foo="bar")
+ Parser, StringIO(), foo="bar")
def test_comments(self):
doc = self.parse(
@@ -262,6 +269,12 @@ class ParserTest(unittest.TestCase):
FeatureLibError, "Font revision numbers must be positive",
self.parse, "table head {FontRevision -17.2;} head;")
+ def test_strict_glyph_name_check(self):
+ self.parse("@bad = [a b ccc];", glyphNames=("a", "b", "ccc"))
+
+ with self.assertRaisesRegex(FeatureLibError, "missing from the glyph set: ccc"):
+ self.parse("@bad = [a b ccc];", glyphNames=("a", "b"))
+
def test_glyphclass(self):
[gc] = self.parse("@dash = [endash emdash figuredash];").statements
self.assertEqual(gc.name, "dash")
@@ -334,6 +347,19 @@ class ParserTest(unittest.TestCase):
[gc] = self.parse("@range = [A-foo.sc - C-foo.sc];", gn).statements
self.assertEqual(gc.glyphSet(), ("A-foo.sc", "B-foo.sc", "C-foo.sc"))
+ def test_glyphclass_ambiguous_dash_no_glyph_names(self):
+ # If Parser is initialized without a glyphNames parameter (or with empty one)
+ # it cannot distinguish between a glyph name containing an hyphen, or a
+ # range of glyph names; thus it will interpret them as literal glyph names
+ # while also outputting a logging warning to alert user about the ambiguity.
+ # https://github.com/fonttools/fonttools/issues/1768
+ glyphNames = ()
+ with CapturingLogHandler("fontTools.feaLib.parser", level="WARNING") as caplog:
+ [gc] = self.parse("@class = [A-foo.sc B-foo.sc C D];", glyphNames).statements
+ self.assertEqual(gc.glyphSet(), ("A-foo.sc", "B-foo.sc", "C", "D"))
+ self.assertEqual(len(caplog.records), 2)
+ caplog.assertRegex("Ambiguous glyph name that looks like a range:")
+
def test_glyphclass_glyph_name_should_win_over_range(self):
# The OpenType Feature File Specification v1.20 makes it clear
# that if a dashed name could be interpreted either as a glyph name
@@ -692,6 +718,18 @@ class ParserTest(unittest.TestCase):
self.assertEqual(flag.asFea(),
"lookupflag RightToLeft MarkAttachmentType @TOP_MARKS;")
+ def test_lookupflag_format_A_MarkAttachmentType_glyphClass(self):
+ flag = self.parse_lookupflag_(
+ "lookupflag RightToLeft MarkAttachmentType [acute grave macron];")
+ self.assertIsInstance(flag, ast.LookupFlagStatement)
+ self.assertEqual(flag.value, 1)
+ self.assertIsInstance(flag.markAttachment, ast.GlyphClass)
+ self.assertEqual(flag.markAttachment.glyphSet(),
+ ("acute", "grave", "macron"))
+ self.assertIsNone(flag.markFilteringSet)
+ self.assertEqual(flag.asFea(),
+ "lookupflag RightToLeft MarkAttachmentType [acute grave macron];")
+
def test_lookupflag_format_A_UseMarkFilteringSet(self):
flag = self.parse_lookupflag_(
"@BOTTOM_MARKS = [cedilla ogonek];"
@@ -705,6 +743,18 @@ class ParserTest(unittest.TestCase):
self.assertEqual(flag.asFea(),
"lookupflag IgnoreLigatures UseMarkFilteringSet @BOTTOM_MARKS;")
+ def test_lookupflag_format_A_UseMarkFilteringSet_glyphClass(self):
+ flag = self.parse_lookupflag_(
+ "lookupflag UseMarkFilteringSet [cedilla ogonek] IgnoreLigatures;")
+ self.assertIsInstance(flag, ast.LookupFlagStatement)
+ self.assertEqual(flag.value, 4)
+ self.assertIsNone(flag.markAttachment)
+ self.assertIsInstance(flag.markFilteringSet, ast.GlyphClass)
+ self.assertEqual(flag.markFilteringSet.glyphSet(),
+ ("cedilla", "ogonek"))
+ self.assertEqual(flag.asFea(),
+ "lookupflag IgnoreLigatures UseMarkFilteringSet [cedilla ogonek];")
+
def test_lookupflag_format_B(self):
flag = self.parse_lookupflag_("lookupflag 7;")
self.assertIsInstance(flag, ast.LookupFlagStatement)
@@ -1039,7 +1089,7 @@ class ParserTest(unittest.TestCase):
self.assertEqual(glyphstr(pos.prefix), "[A a] [B b]")
self.assertEqual(glyphstr(pos.glyphs), "I [N n] P")
self.assertEqual(glyphstr(pos.suffix), "[Y y] [Z z]")
- self.assertEqual(pos.lookups, [lookup1, lookup2, None])
+ self.assertEqual(pos.lookups, [[lookup1], [lookup2], None])
def test_gpos_type_8_lookup_with_values(self):
self.assertRaisesRegex(
@@ -1119,6 +1169,36 @@ class ParserTest(unittest.TestCase):
FeatureLibError, "Expected platform id 1 or 3",
self.parse, 'table name { nameid 9 666 "Foo"; } name;')
+ def test_nameid_hexadecimal(self):
+ doc = self.parse(
+ r'table name { nameid 0x9 0x3 0x1 0x0409 "Test"; } name;')
+ name = doc.statements[0].statements[0]
+ self.assertEqual(name.nameID, 9)
+ self.assertEqual(name.platformID, 3)
+ self.assertEqual(name.platEncID, 1)
+ self.assertEqual(name.langID, 0x0409)
+
+ def test_nameid_octal(self):
+ doc = self.parse(
+ r'table name { nameid 011 03 012 02011 "Test"; } name;')
+ name = doc.statements[0].statements[0]
+ self.assertEqual(name.nameID, 9)
+ self.assertEqual(name.platformID, 3)
+ self.assertEqual(name.platEncID, 10)
+ self.assertEqual(name.langID, 0o2011)
+
+ def test_cv_hexadecimal(self):
+ doc = self.parse(
+ r'feature cv01 { cvParameters { Character 0x5DDE; }; } cv01;')
+ cv = doc.statements[0].statements[0].statements[0]
+ self.assertEqual(cv.character, 0x5DDE)
+
+ def test_cv_octal(self):
+ doc = self.parse(
+ r'feature cv01 { cvParameters { Character 056736; }; } cv01;')
+ cv = doc.statements[0].statements[0].statements[0]
+ self.assertEqual(cv.character, 0o56736)
+
def test_rsub_format_a(self):
doc = self.parse("feature test {rsub a [b B] c' d [e E] by C;} test;")
rsub = doc.statements[0].statements[0]
@@ -1200,6 +1280,76 @@ class ParserTest(unittest.TestCase):
'"dflt" is not a valid script tag; use "DFLT" instead',
self.parse, "feature test {script dflt;} test;")
+ def test_stat_design_axis(self): # STAT DesignAxis
+ doc = self.parse('table STAT { DesignAxis opsz 0 '
+ '{name "Optical Size";}; } STAT;')
+ da = doc.statements[0].statements[0]
+ self.assertIsInstance(da, ast.STATDesignAxisStatement)
+ self.assertEqual(da.tag, 'opsz')
+ self.assertEqual(da.axisOrder, 0)
+ self.assertEqual(da.names[0].string, 'Optical Size')
+
+ def test_stat_axis_value_format1(self): # STAT AxisValue
+ doc = self.parse('table STAT { DesignAxis opsz 0 '
+ '{name "Optical Size";}; '
+ 'AxisValue {location opsz 8; name "Caption";}; } '
+ 'STAT;')
+ avr = doc.statements[0].statements[1]
+ self.assertIsInstance(avr, ast.STATAxisValueStatement)
+ self.assertEqual(avr.locations[0].tag, 'opsz')
+ self.assertEqual(avr.locations[0].values[0], 8)
+ self.assertEqual(avr.names[0].string, 'Caption')
+
+ def test_stat_axis_value_format2(self): # STAT AxisValue
+ doc = self.parse('table STAT { DesignAxis opsz 0 '
+ '{name "Optical Size";}; '
+ 'AxisValue {location opsz 8 6 10; name "Caption";}; } '
+ 'STAT;')
+ avr = doc.statements[0].statements[1]
+ self.assertIsInstance(avr, ast.STATAxisValueStatement)
+ self.assertEqual(avr.locations[0].tag, 'opsz')
+ self.assertEqual(avr.locations[0].values, [8, 6, 10])
+ self.assertEqual(avr.names[0].string, 'Caption')
+
+ def test_stat_axis_value_format2_bad_range(self): # STAT AxisValue
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'Default value 5 is outside of specified range 6-10.',
+ self.parse, 'table STAT { DesignAxis opsz 0 '
+ '{name "Optical Size";}; '
+ 'AxisValue {location opsz 5 6 10; name "Caption";}; } '
+ 'STAT;')
+
+ def test_stat_axis_value_format4(self): # STAT AxisValue
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'Only one value is allowed in a Format 4 Axis Value Record, but 3 were found.',
+ self.parse, 'table STAT { '
+ 'DesignAxis opsz 0 {name "Optical Size";}; '
+ 'DesignAxis wdth 0 {name "Width";}; '
+ 'AxisValue {'
+ 'location opsz 8 6 10; '
+ 'location wdth 400; '
+ 'name "Caption";}; } '
+ 'STAT;')
+
+ def test_stat_elidedfallbackname(self): # STAT ElidedFallbackName
+ doc = self.parse('table STAT { ElidedFallbackName {name "Roman"; '
+ 'name 3 1 0x0411 "ローマン"; }; '
+ '} STAT;')
+ nameRecord = doc.statements[0].statements[0]
+ self.assertIsInstance(nameRecord, ast.ElidedFallbackName)
+ self.assertEqual(nameRecord.names[0].string, 'Roman')
+ self.assertEqual(nameRecord.names[1].string, 'ローマン')
+
+ def test_stat_elidedfallbacknameid(self): # STAT ElidedFallbackNameID
+ doc = self.parse('table name { nameid 278 "Roman"; } name; '
+ 'table STAT { ElidedFallbackNameID 278; '
+ '} STAT;')
+ nameRecord = doc.statements[0].statements[0]
+ self.assertIsInstance(nameRecord, ast.NameRecord)
+ self.assertEqual(nameRecord.string, 'Roman')
+
def test_sub_single_format_a(self): # GSUB LookupType 1
doc = self.parse("feature smcp {substitute a by a.sc;} smcp;")
sub = doc.statements[0].statements[0]
@@ -1368,12 +1518,22 @@ class ParserTest(unittest.TestCase):
" sub f_f by f f;"
" sub f by f;"
" sub f_f_i by f f i;"
+ " sub [a a.sc] by a;"
+ " sub [a a.sc] by [b b.sc];"
"} Look;")
statements = doc.statements[0].statements
for sub in statements:
self.assertIsInstance(sub, ast.MultipleSubstStatement)
self.assertEqual(statements[1].glyph, "f")
self.assertEqual(statements[1].replacement, ["f"])
+ self.assertEqual(statements[3].glyph, "a")
+ self.assertEqual(statements[3].replacement, ["a"])
+ self.assertEqual(statements[4].glyph, "a.sc")
+ self.assertEqual(statements[4].replacement, ["a"])
+ self.assertEqual(statements[5].glyph, "a")
+ self.assertEqual(statements[5].replacement, ["b"])
+ self.assertEqual(statements[6].glyph, "a.sc")
+ self.assertEqual(statements[6].replacement, ["b.sc"])
def test_substitute_from(self): # GSUB LookupType 3
doc = self.parse("feature test {"
@@ -1442,14 +1602,21 @@ class ParserTest(unittest.TestCase):
def test_substitute_lookups(self): # GSUB LookupType 6
doc = Parser(self.getpath("spec5fi1.fea"), GLYPHNAMES).parse()
[_, _, _, langsys, ligs, sub, feature] = doc.statements
- self.assertEqual(feature.statements[0].lookups, [ligs, None, sub])
- self.assertEqual(feature.statements[1].lookups, [ligs, None, sub])
+ self.assertEqual(feature.statements[0].lookups, [[ligs], None, [sub]])
+ self.assertEqual(feature.statements[1].lookups, [[ligs], None, [sub]])
def test_substitute_missing_by(self):
self.assertRaisesRegex(
FeatureLibError,
'Expected "by", "from" or explicit lookup references',
self.parse, "feature liga {substitute f f i;} liga;")
+
+ def test_substitute_invalid_statement(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ "Invalid substitution statement",
+ Parser(self.getpath("GSUB_error.fea"), GLYPHNAMES).parse
+ )
def test_subtable(self):
doc = self.parse("feature test {subtable;} test;")
@@ -1667,8 +1834,14 @@ class ParserTest(unittest.TestCase):
doc = self.parse("table %s { ;;; } %s;" % (table, table))
self.assertEqual(doc.statements[0].statements, [])
+ def test_ufo_features_parse_include_dir(self):
+ fea_path = self.getpath("include/test.ufo/features.fea")
+ include_dir = os.path.dirname(os.path.dirname(fea_path))
+ doc = Parser(fea_path, includeDir=include_dir).parse()
+ assert len(doc.statements) == 1 and doc.statements[0].text == "# Nothing"
+
def parse(self, text, glyphNames=GLYPHNAMES, followIncludes=True):
- featurefile = UnicodeIO(text)
+ featurefile = StringIO(text)
p = Parser(featurefile, glyphNames, followIncludes=followIncludes)
return p.parse()
diff --git a/Tests/fontBuilder/data/test.otf.ttx b/Tests/fontBuilder/data/test.otf.ttx
index 8fdd38f8..7924ef54 100644
--- a/Tests/fontBuilder/data/test.otf.ttx
+++ b/Tests/fontBuilder/data/test.otf.ttx
@@ -266,7 +266,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
diff --git a/Tests/fontBuilder/data/test.ttf.ttx b/Tests/fontBuilder/data/test.ttf.ttx
index 584815ef..8c3f00e1 100644
--- a/Tests/fontBuilder/data/test.ttf.ttx
+++ b/Tests/fontBuilder/data/test.ttf.ttx
@@ -289,7 +289,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="A" out="a"/>
</SingleSubst>
</Lookup>
diff --git a/Tests/fontBuilder/data/test_var.otf.ttx b/Tests/fontBuilder/data/test_var.otf.ttx
index e00d33c5..ff148682 100644
--- a/Tests/fontBuilder/data/test_var.otf.ttx
+++ b/Tests/fontBuilder/data/test_var.otf.ttx
@@ -19,10 +19,10 @@
<unitsPerEm value="1000"/>
<created value="Wed Mar 27 00:23:21 2019"/>
<modified value="Wed Mar 27 00:23:21 2019"/>
- <xMin value="0"/>
- <yMin value="0"/>
- <xMax value="0"/>
- <yMax value="0"/>
+ <xMin value="100"/>
+ <yMin value="100"/>
+ <xMax value="600"/>
+ <yMax value="1000"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="3"/>
<fontDirectionHint value="2"/>
@@ -35,10 +35,10 @@
<ascent value="824"/>
<descent value="200"/>
<lineGap value="0"/>
- <advanceWidthMax value="0"/>
+ <advanceWidthMax value="600"/>
<minLeftSideBearing value="0"/>
- <minRightSideBearing value="0"/>
- <xMaxExtent value="0"/>
+ <minRightSideBearing value="200"/>
+ <xMaxExtent value="400"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
@@ -141,9 +141,6 @@
Test Axis
</namerecord>
<namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
- TotallyNormal
- </namerecord>
- <namerecord nameID="258" platformID="1" platEncID="0" langID="0x0" unicode="True">
TotallyTested
</namerecord>
<namerecord nameID="1" platformID="1" platEncID="0" langID="0x4" unicode="True">
@@ -165,9 +162,6 @@
Test Axis
</namerecord>
<namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
- TotallyNormal
- </namerecord>
- <namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
TotallyTested
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x413">
@@ -213,6 +207,8 @@
<BlueScale value="0.039625"/>
<BlueShift value="7"/>
<BlueFuzz value="1"/>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
</Private>
</FontDict>
</FDArray>
@@ -290,12 +286,12 @@
</Axis>
<!-- TotallyNormal -->
- <NamedInstance flags="0x0" subfamilyNameID="257">
+ <NamedInstance flags="0x0" subfamilyNameID="2">
<coord axis="TEST" value="0.0"/>
</NamedInstance>
<!-- TotallyTested -->
- <NamedInstance flags="0x0" subfamilyNameID="258">
+ <NamedInstance flags="0x0" subfamilyNameID="257">
<coord axis="TEST" value="100.0"/>
</NamedInstance>
</fvar>
diff --git a/Tests/fontBuilder/data/test_var.ttf.ttx b/Tests/fontBuilder/data/test_var.ttf.ttx
index 382d29e1..e6d4d8d5 100644
--- a/Tests/fontBuilder/data/test_var.ttf.ttx
+++ b/Tests/fontBuilder/data/test_var.ttf.ttx
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.32">
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.2">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
@@ -19,7 +19,7 @@
<unitsPerEm value="1024"/>
<created value="Thu Nov 1 20:29:01 2018"/>
<modified value="Thu Nov 1 20:29:01 2018"/>
- <xMin value="100"/>
+ <xMin value="50"/>
<yMin value="0"/>
<xMax value="500"/>
<yMax value="400"/>
@@ -36,7 +36,7 @@
<descent value="200"/>
<lineGap value="0"/>
<advanceWidthMax value="600"/>
- <minLeftSideBearing value="100"/>
+ <minLeftSideBearing value="50"/>
<minRightSideBearing value="100"/>
<xMaxExtent value="500"/>
<caretSlopeRise value="1"/>
@@ -126,7 +126,7 @@
<mtx name=".notdef" width="600" lsb="0"/>
<mtx name=".null" width="600" lsb="0"/>
<mtx name="A" width="600" lsb="100"/>
- <mtx name="a" width="600" lsb="100"/>
+ <mtx name="a" width="600" lsb="50"/>
</hmtx>
<cmap>
@@ -164,12 +164,12 @@
<instructions/>
</TTGlyph>
- <TTGlyph name="a" xMin="100" yMin="0" xMax="500" yMax="400">
+ <TTGlyph name="a" xMin="50" yMin="0" xMax="250" yMax="200">
<contour>
- <pt x="100" y="0" on="1"/>
- <pt x="100" y="400" on="1"/>
- <pt x="500" y="400" on="1"/>
- <pt x="500" y="0" on="1"/>
+ <pt x="50" y="0" on="1"/>
+ <pt x="50" y="200" on="1"/>
+ <pt x="250" y="200" on="1"/>
+ <pt x="250" y="0" on="1"/>
</contour>
<instructions/>
</TTGlyph>
@@ -199,10 +199,10 @@
Down
</namerecord>
<namerecord nameID="260" platformID="1" platEncID="0" langID="0x0" unicode="True">
- TotallyNormal
+ Right Up
</namerecord>
<namerecord nameID="261" platformID="1" platEncID="0" langID="0x0" unicode="True">
- Right Up
+ Neutral
</namerecord>
<namerecord nameID="1" platformID="1" platEncID="0" langID="0x4" unicode="True">
HalloTestFont
@@ -232,10 +232,10 @@
Down
</namerecord>
<namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
- TotallyNormal
+ Right Up
</namerecord>
<namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
- Right Up
+ Neutral
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x413">
HalloTestFont
@@ -269,6 +269,180 @@
</extraNames>
</post>
+ <GSUB>
+ <Version value="0x00010001"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="rclt"/>
+ <Feature>
+ <!-- LookupCount=0 -->
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="A" out="a"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ <FeatureVariations>
+ <Version value="0x00010000"/>
+ <!-- FeatureVariationCount=2 -->
+ <FeatureVariationRecord index="0">
+ <ConditionSet>
+ <!-- ConditionCount=2 -->
+ <ConditionTable index="0" Format="1">
+ <AxisIndex value="3"/>
+ <FilterRangeMinValue value="0.8"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ <ConditionTable index="1" Format="1">
+ <AxisIndex value="0"/>
+ <FilterRangeMinValue value="0.8"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=1 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ <FeatureVariationRecord index="1">
+ <ConditionSet>
+ <!-- ConditionCount=2 -->
+ <ConditionTable index="0" Format="1">
+ <AxisIndex value="1"/>
+ <FilterRangeMinValue value="0.8"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ <ConditionTable index="1" Format="1">
+ <AxisIndex value="2"/>
+ <FilterRangeMinValue value="0.8"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=1 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ </FeatureVariations>
+ </GSUB>
+
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=4 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="LEFT"/>
+ <AxisNameID value="256"/> <!-- Left -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ <Axis index="1">
+ <AxisTag value="RGHT"/>
+ <AxisNameID value="257"/> <!-- Right -->
+ <AxisOrdering value="1"/>
+ </Axis>
+ <Axis index="2">
+ <AxisTag value="UPPP"/>
+ <AxisNameID value="258"/> <!-- Up -->
+ <AxisOrdering value="2"/>
+ </Axis>
+ <Axis index="3">
+ <AxisTag value="DOWN"/>
+ <AxisNameID value="259"/> <!-- Down -->
+ <AxisOrdering value="3"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=8 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="261"/> <!-- Neutral -->
+ <Value value="0.0"/>
+ </AxisValue>
+ <AxisValue index="1" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="256"/> <!-- Left -->
+ <Value value="100.0"/>
+ </AxisValue>
+ <AxisValue index="2" Format="1">
+ <AxisIndex value="1"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="261"/> <!-- Neutral -->
+ <Value value="0.0"/>
+ </AxisValue>
+ <AxisValue index="3" Format="1">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="257"/> <!-- Right -->
+ <Value value="100.0"/>
+ </AxisValue>
+ <AxisValue index="4" Format="1">
+ <AxisIndex value="2"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="261"/> <!-- Neutral -->
+ <Value value="0.0"/>
+ </AxisValue>
+ <AxisValue index="5" Format="1">
+ <AxisIndex value="2"/>
+ <Flags value="0"/>
+ <ValueNameID value="258"/> <!-- Up -->
+ <Value value="100.0"/>
+ </AxisValue>
+ <AxisValue index="6" Format="1">
+ <AxisIndex value="3"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="261"/> <!-- Neutral -->
+ <Value value="0.0"/>
+ </AxisValue>
+ <AxisValue index="7" Format="1">
+ <AxisIndex value="3"/>
+ <Flags value="0"/>
+ <ValueNameID value="259"/> <!-- Down -->
+ <Value value="100.0"/>
+ </AxisValue>
+ </AxisValueArray>
+ <ElidedFallbackNameID value="2"/> <!-- TotallyNormal -->
+ </STAT>
+
<fvar>
<!-- Left -->
@@ -312,7 +486,7 @@
</Axis>
<!-- TotallyNormal -->
- <NamedInstance flags="0x0" subfamilyNameID="260">
+ <NamedInstance flags="0x0" subfamilyNameID="2">
<coord axis="LEFT" value="0.0"/>
<coord axis="RGHT" value="0.0"/>
<coord axis="UPPP" value="0.0"/>
@@ -320,7 +494,7 @@
</NamedInstance>
<!-- Right Up -->
- <NamedInstance flags="0x0" subfamilyNameID="261">
+ <NamedInstance flags="0x0" subfamilyNameID="260">
<coord axis="LEFT" value="0.0"/>
<coord axis="RGHT" value="100.0"/>
<coord axis="UPPP" value="100.0"/>
diff --git a/Tests/fontBuilder/fontBuilder_test.py b/Tests/fontBuilder/fontBuilder_test.py
index 30952846..6368cb87 100644
--- a/Tests/fontBuilder/fontBuilder_test.py
+++ b/Tests/fontBuilder/fontBuilder_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
import os
import pytest
@@ -166,13 +164,20 @@ def test_build_var(tmpdir):
pen.lineTo((500, 400))
pen.lineTo((500, 000))
pen.closePath()
+ glyph1 = pen.glyph()
- glyph = pen.glyph()
+ pen = TTGlyphPen(None)
+ pen.moveTo((50, 0))
+ pen.lineTo((50, 200))
+ pen.lineTo((250, 200))
+ pen.lineTo((250, 0))
+ pen.closePath()
+ glyph2 = pen.glyph()
pen = TTGlyphPen(None)
emptyGlyph = pen.glyph()
- glyphs = {".notdef": emptyGlyph, "A": glyph, "a": glyph, ".null": emptyGlyph}
+ glyphs = {".notdef": emptyGlyph, "A": glyph1, "a": glyph2, ".null": emptyGlyph}
fb.setupGlyf(glyphs)
metrics = {}
glyphTable = fb.font["glyf"]
@@ -208,6 +213,26 @@ def test_build_var(tmpdir):
]
fb.setupGvar(variations)
+ fb.addFeatureVariations(
+ [
+ (
+ [
+ {"LEFT": (0.8, 1), "DOWN": (0.8, 1)},
+ {"RGHT": (0.8, 1), "UPPP": (0.8, 1)},
+ ],
+ {"A": "a"}
+ )
+ ],
+ featureTag="rclt",
+ )
+
+ statAxes = []
+ for tag, minVal, defaultVal, maxVal, name in axes:
+ values = [dict(name="Neutral", value=defaultVal, flags=0x2),
+ dict(name=name, value=maxVal)]
+ statAxes.append(dict(tag=tag, name=name, values=values))
+ fb.setupStat(statAxes)
+
fb.setupOS2()
fb.setupPost()
fb.setupDummyDSIG()
@@ -237,6 +262,19 @@ def test_build_cff2(tmpdir):
_verifyOutput(outPath)
+def test_build_cff_to_cff2(tmpdir):
+ fb, _, _ = _setupFontBuilder(False, 1000)
+
+ pen = T2CharStringPen(600, None)
+ drawTestGlyph(pen)
+ charString = pen.getCharString()
+ charStrings = {".notdef": charString, "A": charString, "a": charString, ".null": charString}
+ fb.setupCFF("TestFont", {}, charStrings, {})
+
+ from fontTools.varLib.cff import convertCFFtoCFF2
+ convertCFFtoCFF2(fb.font)
+
+
def test_setupNameTable_no_mac():
fb, _, nameStrings = _setupFontBuilder(True)
fb.setupNameTable(nameStrings, mac=False)
diff --git a/Tests/merge_test.py b/Tests/merge_test.py
index 00e719b8..015248dd 100644
--- a/Tests/merge_test.py
+++ b/Tests/merge_test.py
@@ -1,7 +1,11 @@
-from fontTools.misc.py23 import *
+import io
+import itertools
from fontTools import ttLib
-from fontTools.merge import *
+from fontTools.ttLib.tables._g_l_y_f import Glyph
+from fontTools.fontBuilder import FontBuilder
+from fontTools.merge import Merger
import unittest
+import pytest
class MergeIntegrationTest(unittest.TestCase):
@@ -113,6 +117,53 @@ class CmapMergeUnitTest(unittest.TestCase):
self.assertEqual(self.merger.duplicateGlyphsPerFont, [{}, {'space#0': 'space#1'}])
+def _compile(ttFont):
+ buf = io.BytesIO()
+ ttFont.save(buf)
+ buf.seek(0)
+ return buf
+
+
+def _make_fontfile_with_OS2(*, version, **kwargs):
+ upem = 1000
+ glyphOrder = [".notdef", "a"]
+ cmap = {0x61: "a"}
+ glyphs = {gn: Glyph() for gn in glyphOrder}
+ hmtx = {gn: (500, 0) for gn in glyphOrder}
+ names = {"familyName": "TestOS2", "styleName": "Regular"}
+
+ fb = FontBuilder(unitsPerEm=upem)
+ fb.setupGlyphOrder(glyphOrder)
+ fb.setupCharacterMap(cmap)
+ fb.setupGlyf(glyphs)
+ fb.setupHorizontalMetrics(hmtx)
+ fb.setupHorizontalHeader()
+ fb.setupNameTable(names)
+ fb.setupOS2(version=version, **kwargs)
+
+ return _compile(fb.font)
+
+
+def _merge_and_recompile(fontfiles, options=None):
+ merger = Merger(options)
+ merged = merger.merge(fontfiles)
+ buf = _compile(merged)
+ return ttLib.TTFont(buf)
+
+
+@pytest.mark.parametrize(
+ "v1, v2", list(itertools.permutations(range(5+1), 2))
+)
+def test_merge_OS2_mixed_versions(v1, v2):
+ # https://github.com/fonttools/fonttools/issues/1865
+ fontfiles = [
+ _make_fontfile_with_OS2(version=v1),
+ _make_fontfile_with_OS2(version=v2),
+ ]
+ merged = _merge_and_recompile(fontfiles)
+ assert merged["OS/2"].version == max(v1, v2)
+
+
if __name__ == "__main__":
import sys
sys.exit(unittest.main())
diff --git a/Tests/misc/arrayTools_test.py b/Tests/misc/arrayTools_test.py
index 2610c0fc..45b186fe 100644
--- a/Tests/misc/arrayTools_test.py
+++ b/Tests/misc/arrayTools_test.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.py23 import round3
from fontTools.misc.arrayTools import (
calcBounds, calcIntBounds, updateBounds, pointInRect, pointsInRect,
vectorLength, asInt16, normRect, scaleRect, offsetRect, insetRect,
@@ -21,7 +18,7 @@ def test_calcIntBounds():
assert calcIntBounds(
[(0.1, 40.1), (0.1, 100.1), (49.9, 49.9), (78.5, 9.5)],
- round=round3
+ round=round
) == (0, 10, 78, 100)
diff --git a/Tests/misc/bezierTools_test.py b/Tests/misc/bezierTools_test.py
index 1519543f..c5cd1b73 100644
--- a/Tests/misc/bezierTools_test.py
+++ b/Tests/misc/bezierTools_test.py
@@ -1,7 +1,5 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.bezierTools import (
- calcQuadraticBounds, calcCubicBounds, splitLine, splitQuadratic,
+ calcQuadraticBounds, calcCubicBounds, segmentPointAtT, splitLine, splitQuadratic,
splitCubic, splitQuadraticAtT, splitCubicAtT, solveCubic)
import pytest
@@ -131,3 +129,22 @@ def test_solveCubic():
assert solveCubic(1.0, -4.5, 6.75, -3.375) == [1.5, 1.5, 1.5]
assert solveCubic(-12.0, 18.0, -9.0, 1.50023651123) == [0.5, 0.5, 0.5]
assert solveCubic(9.0, 0.0, 0.0, -7.62939453125e-05) == [-0.0, -0.0, -0.0]
+
+
+_segmentPointAtT_testData = [
+ ([(0, 10), (200, 100)], 0.0, (0, 10)),
+ ([(0, 10), (200, 100)], 0.5, (100, 55)),
+ ([(0, 10), (200, 100)], 1.0, (200, 100)),
+ ([(0, 10), (100, 100), (200, 50)], 0.0, (0, 10)),
+ ([(0, 10), (100, 100), (200, 50)], 0.5, (100, 65.0)),
+ ([(0, 10), (100, 100), (200, 50)], 1.0, (200, 50.0)),
+ ([(0, 10), (100, 100), (200, 100), (300, 0)], 0.0, (0, 10)),
+ ([(0, 10), (100, 100), (200, 100), (300, 0)], 0.5, (150, 76.25)),
+ ([(0, 10), (100, 100), (200, 100), (300, 0)], 1.0, (300, 0)),
+]
+
+
+@pytest.mark.parametrize("segment, t, expectedPoint", _segmentPointAtT_testData)
+def test_segmentPointAtT(segment, t, expectedPoint):
+ point = segmentPointAtT(segment, t)
+ assert expectedPoint == point
diff --git a/Tests/misc/classifyTools_test.py b/Tests/misc/classifyTools_test.py
index ac1f6565..72a97523 100644
--- a/Tests/misc/classifyTools_test.py
+++ b/Tests/misc/classifyTools_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.classifyTools import classify
diff --git a/Tests/misc/eexec_test.py b/Tests/misc/eexec_test.py
index 20430958..f72760a7 100644
--- a/Tests/misc/eexec_test.py
+++ b/Tests/misc/eexec_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.eexec import decrypt, encrypt
diff --git a/Tests/misc/encodingTools_test.py b/Tests/misc/encodingTools_test.py
index 4c9628b7..1a131f61 100644
--- a/Tests/misc/encodingTools_test.py
+++ b/Tests/misc/encodingTools_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
import unittest
from fontTools.misc.encodingTools import getEncoding
@@ -20,7 +18,7 @@ class EncodingTest(unittest.TestCase):
def test_extended_mac_encodings(self):
encoding = getEncoding(1, 1, 0) # Mac Japanese
decoded = b'\xfe'.decode(encoding)
- self.assertEqual(decoded, unichr(0x2122))
+ self.assertEqual(decoded, chr(0x2122))
def test_extended_unknown(self):
self.assertEqual(getEncoding(10, 11, 12), None)
diff --git a/Tests/misc/etree_test.py b/Tests/misc/etree_test.py
index d2f585cf..3f3232e8 100644
--- a/Tests/misc/etree_test.py
+++ b/Tests/misc/etree_test.py
@@ -1,5 +1,4 @@
# coding: utf-8
-from __future__ import absolute_import, unicode_literals
from fontTools.misc import etree
from collections import OrderedDict
import io
diff --git a/Tests/misc/filenames_test.py b/Tests/misc/filenames_test.py
index 47d137f6..bb7b63c2 100644
--- a/Tests/misc/filenames_test.py
+++ b/Tests/misc/filenames_test.py
@@ -1,4 +1,3 @@
-from __future__ import unicode_literals
import unittest
from fontTools.misc.filenames import (
userNameToFileName, handleClash1, handleClash2)
diff --git a/Tests/misc/fixedTools_test.py b/Tests/misc/fixedTools_test.py
index 5ef17776..dea61b90 100644
--- a/Tests/misc/fixedTools_test.py
+++ b/Tests/misc/fixedTools_test.py
@@ -1,6 +1,11 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import fixedToFloat, floatToFixed
+from fontTools.misc.fixedTools import (
+ fixedToFloat,
+ floatToFixed,
+ floatToFixedToStr,
+ fixedToStr,
+ strToFixed,
+ strToFixedToFloat,
+)
import unittest
@@ -12,19 +17,33 @@ class FixedToolsTest(unittest.TestCase):
self.assertEqual(value, floatToFixed(fixedToFloat(value, bits), bits))
def test_fixedToFloat_precision14(self):
- self.assertEqual(0.8, fixedToFloat(13107, 14))
+ self.assertAlmostEqual(0.7999878, fixedToFloat(13107, 14))
self.assertEqual(0.0, fixedToFloat(0, 14))
self.assertEqual(1.0, fixedToFloat(16384, 14))
self.assertEqual(-1.0, fixedToFloat(-16384, 14))
- self.assertEqual(0.99994, fixedToFloat(16383, 14))
- self.assertEqual(-0.99994, fixedToFloat(-16383, 14))
+ self.assertAlmostEqual(0.999939, fixedToFloat(16383, 14))
+ self.assertAlmostEqual(-0.999939, fixedToFloat(-16383, 14))
def test_fixedToFloat_precision6(self):
- self.assertAlmostEqual(-9.98, fixedToFloat(-639, 6))
+ self.assertAlmostEqual(-9.984375, fixedToFloat(-639, 6))
self.assertAlmostEqual(-10.0, fixedToFloat(-640, 6))
- self.assertAlmostEqual(9.98, fixedToFloat(639, 6))
+ self.assertAlmostEqual(9.984375, fixedToFloat(639, 6))
self.assertAlmostEqual(10.0, fixedToFloat(640, 6))
+ def test_fixedToStr_precision14(self):
+ self.assertEqual('0.8', fixedToStr(13107, 14))
+ self.assertEqual('0.0', fixedToStr(0, 14))
+ self.assertEqual('1.0', fixedToStr(16384, 14))
+ self.assertEqual('-1.0', fixedToStr(-16384, 14))
+ self.assertEqual('0.99994', fixedToStr(16383, 14))
+ self.assertEqual('-0.99994', fixedToStr(-16383, 14))
+
+ def test_fixedToStr_precision6(self):
+ self.assertAlmostEqual('-9.98', fixedToStr(-639, 6))
+ self.assertAlmostEqual('-10.0', fixedToStr(-640, 6))
+ self.assertAlmostEqual('9.98', fixedToStr(639, 6))
+ self.assertAlmostEqual('10.0', fixedToStr(640, 6))
+
def test_floatToFixed_precision14(self):
self.assertEqual(13107, floatToFixed(0.8, 14))
self.assertEqual(16384, floatToFixed(1.0, 14))
@@ -33,6 +52,30 @@ class FixedToolsTest(unittest.TestCase):
self.assertEqual(-16384, floatToFixed(-1, 14))
self.assertEqual(0, floatToFixed(0, 14))
+ def test_strToFixed_precision14(self):
+ self.assertEqual(13107, strToFixed('0.8', 14))
+ self.assertEqual(16384, strToFixed('1.0', 14))
+ self.assertEqual(16384, strToFixed('1', 14))
+ self.assertEqual(-16384, strToFixed('-1.0', 14))
+ self.assertEqual(-16384, strToFixed('-1', 14))
+ self.assertEqual(0, strToFixed('0', 14))
+
+ def test_strToFixedToFloat_precision14(self):
+ self.assertAlmostEqual(0.7999878, strToFixedToFloat('0.8', 14))
+ self.assertEqual(0.0, strToFixedToFloat('0', 14))
+ self.assertEqual(1.0, strToFixedToFloat('1.0', 14))
+ self.assertEqual(-1.0, strToFixedToFloat('-1.0', 14))
+ self.assertAlmostEqual(0.999939, strToFixedToFloat('0.99994', 14))
+ self.assertAlmostEqual(-0.999939, strToFixedToFloat('-0.99994', 14))
+
+ def test_floatToFixedToStr_precision14(self):
+ self.assertEqual('0.8', floatToFixedToStr(0.7999878, 14))
+ self.assertEqual('1.0', floatToFixedToStr(1.0, 14))
+ self.assertEqual('1.0', floatToFixedToStr(1, 14))
+ self.assertEqual('-1.0', floatToFixedToStr(-1.0, 14))
+ self.assertEqual('-1.0', floatToFixedToStr(-1, 14))
+ self.assertEqual('0.0', floatToFixedToStr(0, 14))
+
def test_fixedToFloat_return_float(self):
value = fixedToFloat(16384, 14)
self.assertIsInstance(value, float)
diff --git a/Tests/misc/loggingTools_test.py b/Tests/misc/loggingTools_test.py
index fd64b8b3..fd13044c 100644
--- a/Tests/misc/loggingTools_test.py
+++ b/Tests/misc/loggingTools_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.loggingTools import (
LevelFormatter,
Timer,
@@ -7,6 +5,7 @@ from fontTools.misc.loggingTools import (
ChannelsFilter,
LogMixin,
)
+from io import StringIO
import logging
import textwrap
import time
diff --git a/Tests/misc/macRes_test.py b/Tests/misc/macRes_test.py
index fde13f87..a6a8e9d4 100644
--- a/Tests/misc/macRes_test.py
+++ b/Tests/misc/macRes_test.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from io import BytesIO
import sys
import os
import tempfile
diff --git a/Tests/misc/plistlib_test.py b/Tests/misc/plistlib_test.py
index c8665298..b2ce408d 100644
--- a/Tests/misc/plistlib_test.py
+++ b/Tests/misc/plistlib_test.py
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, unicode_literals
import sys
import os
import datetime
@@ -6,27 +5,15 @@ import codecs
import collections
from io import BytesIO
from numbers import Integral
-from fontTools.misc.py23 import tounicode, unicode
+from fontTools.misc.py23 import tostr
from fontTools.misc import etree
from fontTools.misc import plistlib
from fontTools.ufoLib.plistlib import (
readPlist, readPlistFromString, writePlist, writePlistToString,
)
import pytest
+from collections.abc import Mapping
-try:
- from collections.abc import Mapping # python >= 3.3
-except ImportError:
- from collections import Mapping
-
-PY2 = sys.version_info < (3,)
-if PY2:
- # This is a ResourceWarning that only happens on py27 at interpreter
- # finalization, and only when coverage is enabled. We can ignore it.
- # https://github.com/numpy/numpy/issues/3778#issuecomment-24885336
- pytestmark = pytest.mark.filterwarnings(
- "ignore:tp_compare didn't return -1 or -2 for exception"
- )
# The testdata is generated using https://github.com/python/cpython/...
# Mac/Tools/plistlib_generate_testdata.py
@@ -189,7 +176,7 @@ def test_bytes_string(use_builtin_types):
pl = b"some ASCII bytes"
data = plistlib.dumps(pl, use_builtin_types=False)
pl2 = plistlib.loads(data, use_builtin_types=use_builtin_types)
- assert isinstance(pl2, unicode) # it's always a <string>
+ assert isinstance(pl2, str) # it's always a <string>
assert pl2 == pl.decode()
@@ -434,7 +421,7 @@ def test_fromtree(parametrized_pl):
def _strip(txt):
return (
- "".join(l.strip() for l in tounicode(txt, "utf-8").splitlines())
+ "".join(l.strip() for l in tostr(txt, "utf-8").splitlines())
if txt is not None
else ""
)
@@ -517,15 +504,13 @@ def test_writePlistToString(pl_no_builtin_types):
def test_load_use_builtin_types_default():
pl = plistlib.loads(TESTDATA)
- expected = plistlib.Data if PY2 else bytes
- assert isinstance(pl["someData"], expected)
+ assert isinstance(pl["someData"], bytes)
def test_dump_use_builtin_types_default(pl_no_builtin_types):
data = plistlib.dumps(pl_no_builtin_types)
pl2 = plistlib.loads(data)
- expected = plistlib.Data if PY2 else bytes
- assert isinstance(pl2["someData"], expected)
+ assert isinstance(pl2["someData"], bytes)
assert pl2 == pl_no_builtin_types
diff --git a/Tests/misc/psCharStrings_test.py b/Tests/misc/psCharStrings_test.py
index f69f7481..47ff4fda 100644
--- a/Tests/misc/psCharStrings_test.py
+++ b/Tests/misc/psCharStrings_test.py
@@ -1,10 +1,20 @@
-from __future__ import print_function, division, absolute_import
from fontTools.cffLib import PrivateDict
from fontTools.cffLib.specializer import stringToProgram
-from fontTools.misc.psCharStrings import T2CharString, encodeFloat, read_realNumber
+from fontTools.misc.testTools import getXML, parseXML
+from fontTools.misc.psCharStrings import (
+ T2CharString,
+ encodeFloat,
+ encodeFixed,
+ read_fixed1616,
+ read_realNumber,
+)
import unittest
+def hexenc(s):
+ return ' '.join('%02x' % x for x in s)
+
+
class T2CharStringTest(unittest.TestCase):
@classmethod
@@ -48,14 +58,6 @@ class T2CharStringTest(unittest.TestCase):
'rrcurveto'])
def test_encodeFloat(self):
- import sys
- def hexenc(s):
- return ' '.join('%02x' % ord(x) for x in s)
- if sys.version_info[0] >= 3:
- def hexenc_py3(s):
- return ' '.join('%02x' % x for x in s)
- hexenc = hexenc_py3
-
testNums = [
# value expected result
(-9.399999999999999, '1e e9 a4 ff'), # -9.4
@@ -73,7 +75,7 @@ class T2CharStringTest(unittest.TestCase):
for sample in testNums:
encoded_result = encodeFloat(sample[0])
-
+
# check to see if we got the expected bytes
self.assertEqual(hexenc(encoded_result), sample[1])
@@ -88,6 +90,74 @@ class T2CharStringTest(unittest.TestCase):
# We limit to 8 digits of precision to match the implementation
# of encodeFloat.
+ def test_encode_decode_fixed(self):
+ testNums = [
+ # value expected hex expected float
+ (-9.399999999999999, 'ff ff f6 99 9a', -9.3999939),
+ (-9.4, 'ff ff f6 99 9a', -9.3999939),
+ (9.399999999999999999, 'ff 00 09 66 66', 9.3999939),
+ (9.4, 'ff 00 09 66 66', 9.3999939),
+ (456.8, 'ff 01 c8 cc cd', 456.8000031),
+ (-456.8, 'ff fe 37 33 33', -456.8000031),
+ ]
+
+ for (value, expected_hex, expected_float) in testNums:
+ encoded_result = encodeFixed(value)
+
+ # check to see if we got the expected bytes
+ self.assertEqual(hexenc(encoded_result), expected_hex)
+
+ # check to see if we get the same value by decoding the data
+ decoded_result = read_fixed1616(
+ None,
+ None,
+ encoded_result,
+ 1,
+ )
+ self.assertAlmostEqual(decoded_result[0], expected_float)
+
+ def test_toXML(self):
+ program = [
+ '107 53.4004 166.199 hstem',
+ '174.6 163.801 vstem',
+ '338.4 142.8 rmoveto',
+ '28 0 21.9 9 15.8 18 15.8 18 7.9 20.79959 0 23.6 rrcurveto',
+ 'endchar'
+ ]
+ cs = self.stringToT2CharString(" ".join(program))
+
+ self.assertEqual(getXML(cs.toXML), program)
+
+ def test_fromXML(self):
+ cs = T2CharString()
+ for name, attrs, content in parseXML(
+ [
+ '<CharString name="period">'
+ ' 338.4 142.8 rmoveto',
+ ' 28 0 21.9 9 15.8 18 15.8 18 7.9 20.79959 0 23.6 rrcurveto',
+ ' endchar'
+ '</CharString>'
+ ]
+ ):
+ cs.fromXML(name, attrs, content)
+
+ expected_program = [
+ 338.3999939, 142.8000031, 'rmoveto',
+ 28, 0, 21.8999939, 9, 15.8000031,
+ 18, 15.8000031, 18, 7.8999939,
+ 20.7995911, 0, 23.6000061, 'rrcurveto',
+ 'endchar'
+ ]
+
+ self.assertEqual(len(cs.program), len(expected_program))
+ for arg, expected_arg in zip(cs.program, expected_program):
+ if isinstance(arg, str):
+ self.assertIsInstance(expected_arg, str)
+ self.assertEqual(arg, expected_arg)
+ else:
+ self.assertNotIsInstance(expected_arg, str)
+ self.assertAlmostEqual(arg, expected_arg)
+
if __name__ == "__main__":
import sys
diff --git a/Tests/misc/py23_test.py b/Tests/misc/py23_test.py
index 22b8044e..61274cc2 100644
--- a/Tests/misc/py23_test.py
+++ b/Tests/misc/py23_test.py
@@ -1,7 +1,7 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes
from fontTools.misc.textTools import deHexStr
import filecmp
+from io import StringIO
import tempfile
from subprocess import check_call
import sys
@@ -390,35 +390,6 @@ class IsCloseTests(unittest.TestCase):
self.assertAllNotClose(fraction_examples, rel_tol=1e-9)
-@unittest.skipUnless(
- (sys.version_info[0] == 2 and sys.maxunicode < 0x10FFFF),
- "requires 'narrow' Python 2.7 build")
-class NarrowUnicodeBuildTest(unittest.TestCase):
-
- def test_unichr(self):
- from __builtin__ import unichr as narrow_unichr
-
- self.assertRaises(
- ValueError,
- narrow_unichr, 0xFFFF + 1)
-
- self.assertEqual(unichr(1114111), u'\U0010FFFF')
-
- self.assertRaises(
- ValueError,
- unichr, 0x10FFFF + 1)
-
- def test_byteord(self):
- from __builtin__ import ord as narrow_ord
-
- self.assertRaises(
- TypeError,
- narrow_ord, u'\U00010000')
-
- self.assertEqual(byteord(u'\U00010000'), 0xFFFF + 1)
- self.assertEqual(byteord(u'\U0010FFFF'), 1114111)
-
-
class TestRedirectStream:
redirect_stream = None
diff --git a/Tests/misc/testTools_test.py b/Tests/misc/testTools_test.py
index 11c04b1e..80d4d2ba 100644
--- a/Tests/misc/testTools_test.py
+++ b/Tests/misc/testTools_test.py
@@ -1,7 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
import fontTools.misc.testTools as testTools
import unittest
diff --git a/Tests/misc/textTools_test.py b/Tests/misc/textTools_test.py
index 547569af..f83abf91 100644
--- a/Tests/misc/textTools_test.py
+++ b/Tests/misc/textTools_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import pad
diff --git a/Tests/misc/timeTools_test.py b/Tests/misc/timeTools_test.py
index 6c98f640..4d75ce4e 100644
--- a/Tests/misc/timeTools_test.py
+++ b/Tests/misc/timeTools_test.py
@@ -1,13 +1,12 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.timeTools import asctime, timestampNow, epoch_diff
+from fontTools.misc.timeTools import asctime, timestampNow, timestampToString, timestampFromString, epoch_diff
import os
import time
+import locale
import pytest
def test_asctime():
- assert isinstance(asctime(), basestring)
+ assert isinstance(asctime(), str)
assert asctime(time.gmtime(0)) == 'Thu Jan 1 00:00:00 1970'
@@ -22,3 +21,17 @@ def test_source_date_epoch():
del os.environ["SOURCE_DATE_EPOCH"]
assert timestampNow() + epoch_diff != 150687315
+
+
+# test for issue #1838
+def test_date_parsing_with_locale():
+ l = locale.getlocale(locale.LC_TIME)
+ try:
+ locale.setlocale(locale.LC_TIME, 'de_DE.utf8')
+ except locale.Error:
+ pytest.skip("Locale de_DE not available")
+
+ try:
+ assert timestampFromString(timestampToString(timestampNow()))
+ finally:
+ locale.setlocale(locale.LC_TIME, l)
diff --git a/Tests/misc/transform_test.py b/Tests/misc/transform_test.py
index 7a7a67dc..4efab81f 100644
--- a/Tests/misc/transform_test.py
+++ b/Tests/misc/transform_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.transform import Transform, Identity, Offset, Scale
import math
import pytest
diff --git a/Tests/misc/vector_test.py b/Tests/misc/vector_test.py
new file mode 100644
index 00000000..236a3bad
--- /dev/null
+++ b/Tests/misc/vector_test.py
@@ -0,0 +1,71 @@
+import math
+import pytest
+from fontTools.misc.arrayTools import Vector as ArrayVector
+from fontTools.misc.vector import Vector
+
+
+def test_Vector():
+ v = Vector((100, 200))
+ assert repr(v) == "Vector((100, 200))"
+ assert v == Vector((100, 200))
+ assert v == Vector([100, 200])
+ assert v == (100, 200)
+ assert (100, 200) == v
+ assert v == [100, 200]
+ assert [100, 200] == v
+ assert v is Vector(v)
+ assert v + 10 == (110, 210)
+ assert 10 + v == (110, 210)
+ assert v + Vector((1, 2)) == (101, 202)
+ assert v - Vector((1, 2)) == (99, 198)
+ assert v * 2 == (200, 400)
+ assert 2 * v == (200, 400)
+ assert v * 0.5 == (50, 100)
+ assert v / 2 == (50, 100)
+ assert 2 / v == (0.02, 0.01)
+ v = Vector((3, 4))
+ assert abs(v) == 5 # length
+ assert v.length() == 5
+ assert v.normalized() == Vector((0.6, 0.8))
+ assert abs(Vector((1, 1, 1))) == math.sqrt(3)
+ assert bool(Vector((0, 0, 1)))
+ assert not bool(Vector((0, 0, 0)))
+ v1 = Vector((2, 3))
+ v2 = Vector((3, 4))
+ assert v1.dot(v2) == 18
+ v = Vector((2, 4))
+ assert round(v / 3) == (1, 1)
+ with pytest.raises(
+ AttributeError,
+ match="'Vector' object has no attribute 'newAttr'",
+ ):
+ v.newAttr = 12
+
+
+def test_deprecated():
+ with pytest.warns(
+ DeprecationWarning,
+ match="fontTools.misc.arrayTools.Vector has been deprecated",
+ ):
+ ArrayVector((1, 2))
+ with pytest.warns(
+ DeprecationWarning,
+ match="the 'keep' argument has been deprecated",
+ ):
+ Vector((1, 2), keep=True)
+ v = Vector((1, 2))
+ with pytest.warns(
+ DeprecationWarning,
+ match="the 'toInt' method has been deprecated",
+ ):
+ v.toInt()
+ with pytest.warns(
+ DeprecationWarning,
+ match="the 'values' attribute has been deprecated",
+ ):
+ v.values
+ with pytest.raises(
+ AttributeError,
+ match="the 'values' attribute has been deprecated",
+ ):
+ v.values = [12, 23]
diff --git a/Tests/misc/xmlReader_test.py b/Tests/misc/xmlReader_test.py
index 33fddcc1..f6775cbc 100644
--- a/Tests/misc/xmlReader_test.py
+++ b/Tests/misc/xmlReader_test.py
@@ -1,7 +1,5 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import strjoin
+from io import BytesIO
import os
import unittest
from fontTools.ttLib import TTFont
diff --git a/Tests/misc/xmlWriter_test.py b/Tests/misc/xmlWriter_test.py
index ac8a789e..fd4f2408 100644
--- a/Tests/misc/xmlWriter_test.py
+++ b/Tests/misc/xmlWriter_test.py
@@ -1,5 +1,5 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, tobytes
+from io import BytesIO
import os
import unittest
from fontTools.misc.xmlWriter import XMLWriter
diff --git a/Tests/mtiLib/data/featurename-backward.ttx.GSUB b/Tests/mtiLib/data/featurename-backward.ttx.GSUB
index 9469c792..cc893cd9 100644
--- a/Tests/mtiLib/data/featurename-backward.ttx.GSUB
+++ b/Tests/mtiLib/data/featurename-backward.ttx.GSUB
@@ -51,7 +51,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="a" out="b"/>
</SingleSubst>
</Lookup>
diff --git a/Tests/mtiLib/data/featurename-forward.ttx.GSUB b/Tests/mtiLib/data/featurename-forward.ttx.GSUB
index 9469c792..cc893cd9 100644
--- a/Tests/mtiLib/data/featurename-forward.ttx.GSUB
+++ b/Tests/mtiLib/data/featurename-forward.ttx.GSUB
@@ -51,7 +51,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="a" out="b"/>
</SingleSubst>
</Lookup>
diff --git a/Tests/mtiLib/data/lookupnames-backward.ttx.GSUB b/Tests/mtiLib/data/lookupnames-backward.ttx.GSUB
index 698012c9..cb358d7c 100644
--- a/Tests/mtiLib/data/lookupnames-backward.ttx.GSUB
+++ b/Tests/mtiLib/data/lookupnames-backward.ttx.GSUB
@@ -39,7 +39,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uuvowelsignkannada" out="uuvowelsignaltkannada"/>
<Substitution in="uvowelsignkannada" out="uvowelsignaltkannada"/>
</SingleSubst>
@@ -49,11 +49,11 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uvowelsignkannada"/>
<Glyph value="uuvowelsignkannada"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="pakannada" class="1"/>
<ClassDef glyph="pevowelkannada" class="1"/>
<ClassDef glyph="phakannada" class="1"/>
@@ -61,7 +61,7 @@
<ClassDef glyph="vakannada" class="1"/>
<ClassDef glyph="vevowelkannada" class="1"/>
</BacktrackClassDef>
- <InputClassDef Format="1">
+ <InputClassDef>
<ClassDef glyph="uuvowelsignkannada" class="1"/>
<ClassDef glyph="uvowelsignkannada" class="1"/>
</InputClassDef>
diff --git a/Tests/mtiLib/data/lookupnames-forward.ttx.GSUB b/Tests/mtiLib/data/lookupnames-forward.ttx.GSUB
index 15e48d0b..249d605b 100644
--- a/Tests/mtiLib/data/lookupnames-forward.ttx.GSUB
+++ b/Tests/mtiLib/data/lookupnames-forward.ttx.GSUB
@@ -40,11 +40,11 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uvowelsignkannada"/>
<Glyph value="uuvowelsignkannada"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="pakannada" class="1"/>
<ClassDef glyph="pevowelkannada" class="1"/>
<ClassDef glyph="phakannada" class="1"/>
@@ -52,7 +52,7 @@
<ClassDef glyph="vakannada" class="1"/>
<ClassDef glyph="vevowelkannada" class="1"/>
</BacktrackClassDef>
- <InputClassDef Format="1">
+ <InputClassDef>
<ClassDef glyph="uuvowelsignkannada" class="1"/>
<ClassDef glyph="uvowelsignkannada" class="1"/>
</InputClassDef>
@@ -78,7 +78,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uuvowelsignkannada" out="uuvowelsignaltkannada"/>
<Substitution in="uvowelsignkannada" out="uvowelsignaltkannada"/>
</SingleSubst>
diff --git a/Tests/mtiLib/data/mixed-toplevels.ttx.GSUB b/Tests/mtiLib/data/mixed-toplevels.ttx.GSUB
index 15e48d0b..249d605b 100644
--- a/Tests/mtiLib/data/mixed-toplevels.ttx.GSUB
+++ b/Tests/mtiLib/data/mixed-toplevels.ttx.GSUB
@@ -40,11 +40,11 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uvowelsignkannada"/>
<Glyph value="uuvowelsignkannada"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="pakannada" class="1"/>
<ClassDef glyph="pevowelkannada" class="1"/>
<ClassDef glyph="phakannada" class="1"/>
@@ -52,7 +52,7 @@
<ClassDef glyph="vakannada" class="1"/>
<ClassDef glyph="vevowelkannada" class="1"/>
</BacktrackClassDef>
- <InputClassDef Format="1">
+ <InputClassDef>
<ClassDef glyph="uuvowelsignkannada" class="1"/>
<ClassDef glyph="uvowelsignkannada" class="1"/>
</InputClassDef>
@@ -78,7 +78,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uuvowelsignkannada" out="uuvowelsignaltkannada"/>
<Substitution in="uvowelsignkannada" out="uvowelsignaltkannada"/>
</SingleSubst>
diff --git a/Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS b/Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS
index 811b1df5..b550c700 100644
--- a/Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS
@@ -5,10 +5,10 @@
<!-- LookupCount=2 -->
<Lookup index="0">
<LookupType value="8"/>
- <LookupFlag value="512"/>
+ <LookupFlag value="512"/><!-- markAttachmentType[2] -->
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uuvowelsignsinh"/>
<Glyph value="uvowelsignsinh"/>
</Coverage>
diff --git a/Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB b/Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB
index 67aa9028..7dfdb848 100644
--- a/Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB
@@ -5,10 +5,10 @@
<!-- LookupCount=2 -->
<Lookup index="0">
<LookupType value="6"/>
- <LookupFlag value="512"/>
+ <LookupFlag value="512"/><!-- markAttachmentType[2] -->
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uuvowelsignsinh"/>
<Glyph value="uvowelsignsinh"/>
</Coverage>
diff --git a/Tests/mtiLib/data/mti/chainedclass.ttx.GSUB b/Tests/mtiLib/data/mti/chainedclass.ttx.GSUB
index cfa391f0..fcd7569f 100644
--- a/Tests/mtiLib/data/mti/chainedclass.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/chainedclass.ttx.GSUB
@@ -8,11 +8,11 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uvowelsignkannada"/>
<Glyph value="uuvowelsignkannada"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="pakannada" class="1"/>
<ClassDef glyph="pevowelkannada" class="1"/>
<ClassDef glyph="phakannada" class="1"/>
@@ -20,7 +20,7 @@
<ClassDef glyph="vakannada" class="1"/>
<ClassDef glyph="vevowelkannada" class="1"/>
</BacktrackClassDef>
- <InputClassDef Format="1">
+ <InputClassDef>
<ClassDef glyph="uuvowelsignkannada" class="1"/>
<ClassDef glyph="uvowelsignkannada" class="1"/>
</InputClassDef>
@@ -46,7 +46,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uuvowelsignkannada" out="uuvowelsignaltkannada"/>
<Substitution in="uvowelsignkannada" out="uvowelsignaltkannada"/>
</SingleSubst>
diff --git a/Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB b/Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB
index 7c807a4c..4f312c6e 100644
--- a/Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB
@@ -9,7 +9,7 @@
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="2">
+ <BacktrackCoverage index="0">
<Glyph value="zero"/>
<Glyph value="one"/>
<Glyph value="two"/>
@@ -22,11 +22,11 @@
<Glyph value="nine"/>
</BacktrackCoverage>
<!-- InputGlyphCount=1 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="slash"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="2">
+ <LookAheadCoverage index="0">
<Glyph value="zero"/>
<Glyph value="one"/>
<Glyph value="two"/>
@@ -49,7 +49,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="slash" out="fraction"/>
</SingleSubst>
</Lookup>
diff --git a/Tests/mtiLib/data/mti/gdefattach.ttx.GDEF b/Tests/mtiLib/data/mti/gdefattach.ttx.GDEF
index cc4c70df..79aed8f1 100644
--- a/Tests/mtiLib/data/mti/gdefattach.ttx.GDEF
+++ b/Tests/mtiLib/data/mti/gdefattach.ttx.GDEF
@@ -2,7 +2,7 @@
<GDEF>
<Version value="0x00010000"/>
<AttachList>
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="B"/>
</Coverage>
diff --git a/Tests/mtiLib/data/mti/gdefclasses.ttx.GDEF b/Tests/mtiLib/data/mti/gdefclasses.ttx.GDEF
index e298c288..c3c9d68d 100644
--- a/Tests/mtiLib/data/mti/gdefclasses.ttx.GDEF
+++ b/Tests/mtiLib/data/mti/gdefclasses.ttx.GDEF
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="C" class="1"/>
<ClassDef glyph="acute" class="3"/>
diff --git a/Tests/mtiLib/data/mti/gdefligcaret.ttx.GDEF b/Tests/mtiLib/data/mti/gdefligcaret.ttx.GDEF
index 48f73479..174af8e2 100644
--- a/Tests/mtiLib/data/mti/gdefligcaret.ttx.GDEF
+++ b/Tests/mtiLib/data/mti/gdefligcaret.ttx.GDEF
@@ -2,7 +2,7 @@
<GDEF>
<Version value="0x00010000"/>
<LigCaretList>
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uniFB01"/>
<Glyph value="ffi"/>
</Coverage>
diff --git a/Tests/mtiLib/data/mti/gdefmarkattach.ttx.GDEF b/Tests/mtiLib/data/mti/gdefmarkattach.ttx.GDEF
index b6cbe10b..97683ab3 100644
--- a/Tests/mtiLib/data/mti/gdefmarkattach.ttx.GDEF
+++ b/Tests/mtiLib/data/mti/gdefmarkattach.ttx.GDEF
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<GDEF>
<Version value="0x00010000"/>
- <MarkAttachClassDef Format="1">
+ <MarkAttachClassDef>
<ClassDef glyph="breve" class="1"/>
<ClassDef glyph="commaacent" class="2"/>
<ClassDef glyph="dotbelow" class="2"/>
diff --git a/Tests/mtiLib/data/mti/gdefmarkfilter.ttx.GDEF b/Tests/mtiLib/data/mti/gdefmarkfilter.ttx.GDEF
index 3b234811..4f3593ea 100644
--- a/Tests/mtiLib/data/mti/gdefmarkfilter.ttx.GDEF
+++ b/Tests/mtiLib/data/mti/gdefmarkfilter.ttx.GDEF
@@ -5,17 +5,17 @@
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=4 -->
<Coverage index="0" empty="1"/>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="breve"/>
<Glyph value="acute"/>
<Glyph value="dotabove"/>
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
<Glyph value="dotbelow"/>
<Glyph value="cedilla"/>
<Glyph value="commaaccent"/>
</Coverage>
- <Coverage index="3" Format="1">
+ <Coverage index="3">
<Glyph value="dotbelow"/>
<Glyph value="dotabove"/>
</Coverage>
diff --git a/Tests/mtiLib/data/mti/gposcursive.ttx.GPOS b/Tests/mtiLib/data/mti/gposcursive.ttx.GPOS
index 29651391..6c08c50c 100644
--- a/Tests/mtiLib/data/mti/gposcursive.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gposcursive.ttx.GPOS
@@ -8,7 +8,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<CursivePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="B"/>
</Coverage>
diff --git a/Tests/mtiLib/data/mti/gposkernset.ttx.GPOS b/Tests/mtiLib/data/mti/gposkernset.ttx.GPOS
index edfea10c..a8371233 100644
--- a/Tests/mtiLib/data/mti/gposkernset.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gposkernset.ttx.GPOS
@@ -8,7 +8,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=2 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="Acircumflex"/>
<Glyph value="T"/>
</Coverage>
@@ -31,7 +31,7 @@
</PairSet>
</PairPos>
<PairPos index="1" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="Acircumflex"/>
<Glyph value="T"/>
@@ -44,7 +44,7 @@
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Aacute" class="1"/>
<ClassDef glyph="Acircumflex" class="1"/>
@@ -55,7 +55,7 @@
<ClassDef glyph="Ograve" class="2"/>
<ClassDef glyph="T" class="3"/>
</ClassDef1>
- <ClassDef2 Format="2">
+ <ClassDef2>
<ClassDef glyph="V" class="1"/>
<ClassDef glyph="a" class="2"/>
<ClassDef glyph="aacute" class="2"/>
diff --git a/Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS b/Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS
index b0a74e70..e6e21028 100644
--- a/Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS
@@ -8,7 +8,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="2">
+ <MarkCoverage>
<Glyph value="aimatrabindigurmukhi"/>
<Glyph value="aimatragurmukhi"/>
<Glyph value="aimatratippigurmukhi"/>
@@ -22,7 +22,7 @@
<Glyph value="oomatragurmukhi"/>
<Glyph value="oomatratippigurmukhi"/>
</MarkCoverage>
- <BaseCoverage Format="2">
+ <BaseCoverage>
<Glyph value="lagurmukhi"/>
<Glyph value="lanuktagurmukhi"/>
<Glyph value="nagurmukhi"/>
diff --git a/Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS b/Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS
index 567b2a79..32b35aee 100644
--- a/Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS
@@ -8,7 +8,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="Acircumflex"/>
<Glyph value="T"/>
@@ -21,7 +21,7 @@
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Aacute" class="1"/>
<ClassDef glyph="Acircumflex" class="1"/>
@@ -32,7 +32,7 @@
<ClassDef glyph="Ograve" class="2"/>
<ClassDef glyph="T" class="3"/>
</ClassDef1>
- <ClassDef2 Format="2">
+ <ClassDef2>
<ClassDef glyph="V" class="1"/>
<ClassDef glyph="a" class="2"/>
<ClassDef glyph="aacute" class="2"/>
diff --git a/Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS b/Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS
index ea0161b3..f03a90e3 100644
--- a/Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS
@@ -8,7 +8,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="Acircumflex"/>
<Glyph value="T"/>
diff --git a/Tests/mtiLib/data/mti/gpossingle.ttx.GPOS b/Tests/mtiLib/data/mti/gpossingle.ttx.GPOS
index adbb44ff..c3bdbf68 100644
--- a/Tests/mtiLib/data/mti/gpossingle.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gpossingle.ttx.GPOS
@@ -8,7 +8,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="2">
+ <Coverage>
<Glyph value="bsuperior"/>
<Glyph value="isuperior"/>
<Glyph value="vsuperior"/>
diff --git a/Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB b/Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB
index 41843252..86b0b731 100644
--- a/Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB
@@ -7,7 +7,7 @@
<LookupType value="3"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <AlternateSubst index="0" Format="1">
+ <AlternateSubst index="0">
<AlternateSet glyph="eight">
<Alternate glyph="uniF738"/>
<Alternate glyph="uniE0C0"/>
diff --git a/Tests/mtiLib/data/mti/gsubligature.ttx.GSUB b/Tests/mtiLib/data/mti/gsubligature.ttx.GSUB
index ad8f5054..26c88c81 100644
--- a/Tests/mtiLib/data/mti/gsubligature.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/gsubligature.ttx.GSUB
@@ -7,7 +7,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="I">
<Ligature components="J" glyph="IJ"/>
</LigatureSet>
diff --git a/Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB b/Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB
index a68a45a9..5bedfba6 100644
--- a/Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB
@@ -7,7 +7,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="janyevoweltelugu" out="jaivoweltelugu,nyasubscripttelugu"/>
<Substitution in="kassevoweltelugu" out="kaivoweltelugu,ssasubscripttelugu"/>
</MultipleSubst>
diff --git a/Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB b/Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB
index 62ba14fe..d705af53 100644
--- a/Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB
@@ -5,17 +5,17 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="8"/>
- <LookupFlag value="9"/>
+ <LookupFlag value="9"/><!-- rightToLeft ignoreMarks -->
<!-- SubTableCount=3 -->
<ReverseChainSingleSubst index="0" Format="1">
- <Coverage Format="2">
+ <Coverage>
<Glyph value="rayf2"/>
<Glyph value="reyf2"/>
<Glyph value="yayf2"/>
<Glyph value="zayf2"/>
</Coverage>
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="2">
+ <BacktrackCoverage index="0">
<Glyph value="bayi1"/>
<Glyph value="jeemi1"/>
<Glyph value="kafi1"/>
@@ -33,14 +33,14 @@
<Substitute index="3" value="zayf1"/>
</ReverseChainSingleSubst>
<ReverseChainSingleSubst index="1" Format="1">
- <Coverage Format="2">
+ <Coverage>
<Glyph value="ayehf2"/>
<Glyph value="hamzayeharabf2"/>
<Glyph value="hamzayehf2"/>
<Glyph value="yehf2"/>
</Coverage>
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="bayi1"/>
<Glyph value="kafi1"/>
<Glyph value="ghafi1"/>
@@ -58,14 +58,14 @@
<Substitute index="3" value="yehf1"/>
</ReverseChainSingleSubst>
<ReverseChainSingleSubst index="2" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="dal"/>
<Glyph value="del"/>
<Glyph value="zal"/>
</Coverage>
<!-- BacktrackGlyphCount=0 -->
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="2">
+ <LookAheadCoverage index="0">
<Glyph value="ray"/>
<Glyph value="rey"/>
<Glyph value="zay"/>
diff --git a/Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB b/Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB
index 525b365f..dc6a2950 100644
--- a/Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB
@@ -7,7 +7,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="onehalf" out="onehalf.alt"/>
<Substitution in="onequarter" out="onequarter.alt"/>
<Substitution in="threequarters" out="threequarters.alt"/>
diff --git a/Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS b/Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS
index 7e02fe0c..b5f275eb 100644
--- a/Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS
@@ -8,7 +8,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkLigPos index="0" Format="1">
- <MarkCoverage Format="2">
+ <MarkCoverage>
<Glyph value="AlefSuperiorNS"/>
<Glyph value="DammaNS"/>
<Glyph value="DammaRflxNS"/>
@@ -37,7 +37,7 @@
<Glyph value="UltapeshNS"/>
<Glyph value="WaslaNS"/>
</MarkCoverage>
- <LigatureCoverage Format="2">
+ <LigatureCoverage>
<Glyph value="AinIni.12m_MeemFin.02"/>
<Glyph value="AinIni_YehBarreeFin"/>
<Glyph value="AinMed_YehBarreeFin"/>
diff --git a/Tests/mtiLib/mti_test.py b/Tests/mtiLib/mti_test.py
index 49168286..f0e7bdf3 100644
--- a/Tests/mtiLib/mti_test.py
+++ b/Tests/mtiLib/mti_test.py
@@ -1,10 +1,8 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib import TTFont
from fontTools import mtiLib
import difflib
+from io import StringIO
import os
import sys
import unittest
diff --git a/Tests/otlLib/__init__.py b/Tests/otlLib/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/Tests/otlLib/__init__.py
diff --git a/Tests/otlLib/builder_test.py b/Tests/otlLib/builder_test.py
index 83f7c828..1c2c324d 100644
--- a/Tests/otlLib/builder_test.py
+++ b/Tests/otlLib/builder_test.py
@@ -1,7 +1,9 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
+import io
+import struct
+from fontTools.misc.fixedTools import floatToFixed
from fontTools.misc.testTools import getXML
-from fontTools.otlLib import builder
+from fontTools.otlLib import builder, error
+from fontTools import ttLib
from fontTools.ttLib.tables import otTables
import pytest
@@ -472,7 +474,7 @@ class BuilderTest(object):
assert getXML(lookup.toXML) == [
"<Lookup>",
' <LookupType value="1"/>',
- ' <LookupFlag value="7"/>',
+ ' <LookupFlag value="7"/><!-- rightToLeft ignoreBaseGlyphs ignoreLigatures -->',
" <!-- SubTableCount=2 -->",
' <SingleSubst index="0">',
' <Substitution in="one" out="two"/>',
@@ -493,14 +495,6 @@ class BuilderTest(object):
),
) as excinfo:
builder.buildLookup([s], builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET, None)
- with pytest.raises(
- AssertionError,
- match=(
- "if markFilterSet is not None, flags must set "
- "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x0004"
- ),
- ) as excinfo:
- builder.buildLookup([s], builder.LOOKUP_FLAG_IGNORE_LIGATURES, 777)
def test_buildLookup_conflictingSubtableTypes(self):
s1 = builder.buildSingleSubstSubtable({"one": "two"})
@@ -526,7 +520,7 @@ class BuilderTest(object):
assert getXML(lookup.toXML) == [
"<Lookup>",
' <LookupType value="1"/>',
- ' <LookupFlag value="17"/>',
+ ' <LookupFlag value="17"/><!-- rightToLeft useMarkFilteringSet -->',
" <!-- SubTableCount=1 -->",
' <SingleSubst index="0">',
' <Substitution in="one" out="two"/>',
@@ -845,8 +839,12 @@ class BuilderTest(object):
" <!-- Class2Count=3 -->",
' <Class1Record index="0">',
' <Class2Record index="0">',
+ ' <Value1 XPlacement="0" YPlacement="0"/>',
+ ' <Value2 XPlacement="0"/>',
" </Class2Record>",
' <Class2Record index="1">',
+ ' <Value1 XPlacement="0" YPlacement="0"/>',
+ ' <Value2 XPlacement="0"/>',
" </Class2Record>",
' <Class2Record index="2">',
' <Value1 XPlacement="-80" YPlacement="-20"/>',
@@ -855,12 +853,15 @@ class BuilderTest(object):
" </Class1Record>",
' <Class1Record index="1">',
' <Class2Record index="0">',
+ ' <Value1 XPlacement="0" YPlacement="0"/>',
+ ' <Value2 XPlacement="0"/>',
" </Class2Record>",
' <Class2Record index="1">',
+ ' <Value1 XPlacement="0" YPlacement="0"/>',
' <Value2 XPlacement="-20"/>',
" </Class2Record>",
' <Class2Record index="2">',
- " <Value1/>",
+ ' <Value1 XPlacement="0" YPlacement="0"/>',
' <Value2 XPlacement="-50"/>',
" </Class2Record>",
" </Class1Record>",
@@ -917,9 +918,11 @@ class BuilderTest(object):
("A", "zero"): (d0, d50),
("A", "one"): (None, d20),
("B", "five"): (d8020, d50),
+
},
self.GLYPHMAP,
)
+
assert getXML(subtable.toXML) == [
'<PairPos Format="1">',
" <Coverage>",
@@ -933,10 +936,12 @@ class BuilderTest(object):
" <!-- PairValueCount=2 -->",
' <PairValueRecord index="0">',
' <SecondGlyph value="zero"/>',
+ ' <Value1 XPlacement="0" YPlacement="0"/>',
' <Value2 XPlacement="-50"/>',
" </PairValueRecord>",
' <PairValueRecord index="1">',
' <SecondGlyph value="one"/>',
+ ' <Value1 XPlacement="0" YPlacement="0"/>',
' <Value2 XPlacement="-20"/>',
" </PairValueRecord>",
" </PairSet>",
@@ -1037,8 +1042,8 @@ class BuilderTest(object):
" </Coverage>",
' <ValueFormat value="3"/>',
" <!-- ValueCount=2 -->",
- ' <Value index="0" XPlacement="777"/>',
- ' <Value index="1" YPlacement="-888"/>',
+ ' <Value index="0" XPlacement="777" YPlacement="0"/>',
+ ' <Value index="1" XPlacement="0" YPlacement="-888"/>',
"</SinglePos>",
]
@@ -1107,6 +1112,353 @@ class ClassDefBuilderTest(object):
assert not b.canAdd({"d", "e", "f"})
assert not b.canAdd({"f"})
+ def test_add_exception(self):
+ b = builder.ClassDefBuilder(useClass0=True)
+ b.add({"a", "b", "c"})
+ with pytest.raises(error.OpenTypeLibError):
+ b.add({"a", "d"})
+
+
+buildStatTable_test_data = [
+ ([
+ dict(
+ tag="wght",
+ name="Weight",
+ values=[
+ dict(value=100, name='Thin'),
+ dict(value=400, name='Regular', flags=0x2),
+ dict(value=900, name='Black')])], None, "Regular", [
+ ' <STAT>',
+ ' <Version value="0x00010001"/>',
+ ' <DesignAxisRecordSize value="8"/>',
+ ' <!-- DesignAxisCount=1 -->',
+ ' <DesignAxisRecord>',
+ ' <Axis index="0">',
+ ' <AxisTag value="wght"/>',
+ ' <AxisNameID value="257"/> <!-- Weight -->',
+ ' <AxisOrdering value="0"/>',
+ ' </Axis>',
+ ' </DesignAxisRecord>',
+ ' <!-- AxisValueCount=3 -->',
+ ' <AxisValueArray>',
+ ' <AxisValue index="0" Format="1">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="0"/>',
+ ' <ValueNameID value="258"/> <!-- Thin -->',
+ ' <Value value="100.0"/>',
+ ' </AxisValue>',
+ ' <AxisValue index="1" Format="1">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="2"/> <!-- ElidableAxisValueName -->',
+ ' <ValueNameID value="256"/> <!-- Regular -->',
+ ' <Value value="400.0"/>',
+ ' </AxisValue>',
+ ' <AxisValue index="2" Format="1">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="0"/>',
+ ' <ValueNameID value="259"/> <!-- Black -->',
+ ' <Value value="900.0"/>',
+ ' </AxisValue>',
+ ' </AxisValueArray>',
+ ' <ElidedFallbackNameID value="256"/> <!-- Regular -->',
+ ' </STAT>']),
+ ([
+ dict(
+ tag="wght",
+ name=dict(en="Weight", nl="Gewicht"),
+ values=[
+ dict(value=100, name=dict(en='Thin', nl='Dun')),
+ dict(value=400, name='Regular', flags=0x2),
+ dict(value=900, name='Black'),
+ ]),
+ dict(
+ tag="wdth",
+ name="Width",
+ values=[
+ dict(value=50, name='Condensed'),
+ dict(value=100, name='Regular', flags=0x2),
+ dict(value=200, name='Extended')])], None, 2, [
+ ' <STAT>',
+ ' <Version value="0x00010001"/>',
+ ' <DesignAxisRecordSize value="8"/>',
+ ' <!-- DesignAxisCount=2 -->',
+ ' <DesignAxisRecord>',
+ ' <Axis index="0">',
+ ' <AxisTag value="wght"/>',
+ ' <AxisNameID value="256"/> <!-- Weight -->',
+ ' <AxisOrdering value="0"/>',
+ ' </Axis>',
+ ' <Axis index="1">',
+ ' <AxisTag value="wdth"/>',
+ ' <AxisNameID value="260"/> <!-- Width -->',
+ ' <AxisOrdering value="1"/>',
+ ' </Axis>',
+ ' </DesignAxisRecord>',
+ ' <!-- AxisValueCount=6 -->',
+ ' <AxisValueArray>',
+ ' <AxisValue index="0" Format="1">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="0"/>',
+ ' <ValueNameID value="257"/> <!-- Thin -->',
+ ' <Value value="100.0"/>',
+ ' </AxisValue>',
+ ' <AxisValue index="1" Format="1">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="2"/> <!-- ElidableAxisValueName -->',
+ ' <ValueNameID value="258"/> <!-- Regular -->',
+ ' <Value value="400.0"/>',
+ ' </AxisValue>',
+ ' <AxisValue index="2" Format="1">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="0"/>',
+ ' <ValueNameID value="259"/> <!-- Black -->',
+ ' <Value value="900.0"/>',
+ ' </AxisValue>',
+ ' <AxisValue index="3" Format="1">',
+ ' <AxisIndex value="1"/>',
+ ' <Flags value="0"/>',
+ ' <ValueNameID value="261"/> <!-- Condensed -->',
+ ' <Value value="50.0"/>',
+ ' </AxisValue>',
+ ' <AxisValue index="4" Format="1">',
+ ' <AxisIndex value="1"/>',
+ ' <Flags value="2"/> <!-- ElidableAxisValueName -->',
+ ' <ValueNameID value="258"/> <!-- Regular -->',
+ ' <Value value="100.0"/>',
+ ' </AxisValue>',
+ ' <AxisValue index="5" Format="1">',
+ ' <AxisIndex value="1"/>',
+ ' <Flags value="0"/>',
+ ' <ValueNameID value="262"/> <!-- Extended -->',
+ ' <Value value="200.0"/>',
+ ' </AxisValue>',
+ ' </AxisValueArray>',
+ ' <ElidedFallbackNameID value="2"/> <!-- missing from name table -->',
+ ' </STAT>']),
+ ([
+ dict(
+ tag="wght",
+ name="Weight",
+ values=[
+ dict(value=400, name='Regular', flags=0x2),
+ dict(value=600, linkedValue=650, name='Bold')])], None, 18, [
+ ' <STAT>',
+ ' <Version value="0x00010001"/>',
+ ' <DesignAxisRecordSize value="8"/>',
+ ' <!-- DesignAxisCount=1 -->',
+ ' <DesignAxisRecord>',
+ ' <Axis index="0">',
+ ' <AxisTag value="wght"/>',
+ ' <AxisNameID value="256"/> <!-- Weight -->',
+ ' <AxisOrdering value="0"/>',
+ ' </Axis>',
+ ' </DesignAxisRecord>',
+ ' <!-- AxisValueCount=2 -->',
+ ' <AxisValueArray>',
+ ' <AxisValue index="0" Format="1">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="2"/> <!-- ElidableAxisValueName -->',
+ ' <ValueNameID value="257"/> <!-- Regular -->',
+ ' <Value value="400.0"/>',
+ ' </AxisValue>',
+ ' <AxisValue index="1" Format="3">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="0"/>',
+ ' <ValueNameID value="258"/> <!-- Bold -->',
+ ' <Value value="600.0"/>',
+ ' <LinkedValue value="650.0"/>',
+ ' </AxisValue>',
+ ' </AxisValueArray>',
+ ' <ElidedFallbackNameID value="18"/> <!-- missing from name table -->',
+ ' </STAT>']),
+ ([
+ dict(
+ tag="opsz",
+ name="Optical Size",
+ values=[
+ dict(nominalValue=6, rangeMaxValue=10, name='Small'),
+ dict(rangeMinValue=10, nominalValue=14, rangeMaxValue=24, name='Text', flags=0x2),
+ dict(rangeMinValue=24, nominalValue=600, name='Display')])], None, 2, [
+ ' <STAT>',
+ ' <Version value="0x00010001"/>',
+ ' <DesignAxisRecordSize value="8"/>',
+ ' <!-- DesignAxisCount=1 -->',
+ ' <DesignAxisRecord>',
+ ' <Axis index="0">',
+ ' <AxisTag value="opsz"/>',
+ ' <AxisNameID value="256"/> <!-- Optical Size -->',
+ ' <AxisOrdering value="0"/>',
+ ' </Axis>',
+ ' </DesignAxisRecord>',
+ ' <!-- AxisValueCount=3 -->',
+ ' <AxisValueArray>',
+ ' <AxisValue index="0" Format="2">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="0"/>',
+ ' <ValueNameID value="257"/> <!-- Small -->',
+ ' <NominalValue value="6.0"/>',
+ ' <RangeMinValue value="-32768.0"/>',
+ ' <RangeMaxValue value="10.0"/>',
+ ' </AxisValue>',
+ ' <AxisValue index="1" Format="2">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="2"/> <!-- ElidableAxisValueName -->',
+ ' <ValueNameID value="258"/> <!-- Text -->',
+ ' <NominalValue value="14.0"/>',
+ ' <RangeMinValue value="10.0"/>',
+ ' <RangeMaxValue value="24.0"/>',
+ ' </AxisValue>',
+ ' <AxisValue index="2" Format="2">',
+ ' <AxisIndex value="0"/>',
+ ' <Flags value="0"/>',
+ ' <ValueNameID value="259"/> <!-- Display -->',
+ ' <NominalValue value="600.0"/>',
+ ' <RangeMinValue value="24.0"/>',
+ ' <RangeMaxValue value="32767.99998"/>',
+ ' </AxisValue>',
+ ' </AxisValueArray>',
+ ' <ElidedFallbackNameID value="2"/> <!-- missing from name table -->',
+ ' </STAT>']),
+ ([
+ dict(
+ tag="wght",
+ name="Weight",
+ ordering=1,
+ values=[]),
+ dict(
+ tag="ABCD",
+ name="ABCDTest",
+ ordering=0,
+ values=[
+ dict(value=100, name="Regular", flags=0x2)])],
+ [dict(location=dict(wght=300, ABCD=100), name='Regular ABCD')], 18, [
+ ' <STAT>',
+ ' <Version value="0x00010002"/>',
+ ' <DesignAxisRecordSize value="8"/>',
+ ' <!-- DesignAxisCount=2 -->',
+ ' <DesignAxisRecord>',
+ ' <Axis index="0">',
+ ' <AxisTag value="wght"/>',
+ ' <AxisNameID value="256"/> <!-- Weight -->',
+ ' <AxisOrdering value="1"/>',
+ ' </Axis>',
+ ' <Axis index="1">',
+ ' <AxisTag value="ABCD"/>',
+ ' <AxisNameID value="257"/> <!-- ABCDTest -->',
+ ' <AxisOrdering value="0"/>',
+ ' </Axis>',
+ ' </DesignAxisRecord>',
+ ' <!-- AxisValueCount=2 -->',
+ ' <AxisValueArray>',
+ ' <AxisValue index="0" Format="4">',
+ ' <!-- AxisCount=2 -->',
+ ' <Flags value="0"/>',
+ ' <ValueNameID value="259"/> <!-- Regular ABCD -->',
+ ' <AxisValueRecord index="0">',
+ ' <AxisIndex value="0"/>',
+ ' <Value value="300.0"/>',
+ ' </AxisValueRecord>',
+ ' <AxisValueRecord index="1">',
+ ' <AxisIndex value="1"/>',
+ ' <Value value="100.0"/>',
+ ' </AxisValueRecord>',
+ ' </AxisValue>',
+ ' <AxisValue index="1" Format="1">',
+ ' <AxisIndex value="1"/>',
+ ' <Flags value="2"/> <!-- ElidableAxisValueName -->',
+ ' <ValueNameID value="258"/> <!-- Regular -->',
+ ' <Value value="100.0"/>',
+ ' </AxisValue>',
+ ' </AxisValueArray>',
+ ' <ElidedFallbackNameID value="18"/> <!-- missing from name table -->',
+ ' </STAT>']),
+]
+
+
+@pytest.mark.parametrize("axes, axisValues, elidedFallbackName, expected_ttx", buildStatTable_test_data)
+def test_buildStatTable(axes, axisValues, elidedFallbackName, expected_ttx):
+ font = ttLib.TTFont()
+ font["name"] = ttLib.newTable("name")
+ font["name"].names = []
+ # https://github.com/fonttools/fonttools/issues/1985
+ # Add nameID < 256 that matches a test axis name, to test whether
+ # the nameID is not reused: AxisNameIDs must be > 255 according
+ # to the spec.
+ font["name"].addMultilingualName(dict(en="ABCDTest"), nameID=6)
+ builder.buildStatTable(font, axes, axisValues, elidedFallbackName)
+ f = io.StringIO()
+ font.saveXML(f, tables=["STAT"])
+ ttx = f.getvalue().splitlines()
+ ttx = ttx[3:-2] # strip XML header and <ttFont> element
+ assert expected_ttx == ttx
+ # Compile and round-trip
+ f = io.BytesIO()
+ font.save(f)
+ font = ttLib.TTFont(f)
+ f = io.StringIO()
+ font.saveXML(f, tables=["STAT"])
+ ttx = f.getvalue().splitlines()
+ ttx = ttx[3:-2] # strip XML header and <ttFont> element
+ assert expected_ttx == ttx
+
+
+def test_stat_infinities():
+ negInf = floatToFixed(builder.AXIS_VALUE_NEGATIVE_INFINITY, 16)
+ assert struct.pack(">l", negInf) == b"\x80\x00\x00\x00"
+ posInf = floatToFixed(builder.AXIS_VALUE_POSITIVE_INFINITY, 16)
+ assert struct.pack(">l", posInf) == b"\x7f\xff\xff\xff"
+
+
+class ChainContextualRulesetTest(object):
+ def test_makeRulesets(self):
+ font = ttLib.TTFont()
+ font.setGlyphOrder(["a","b","c","d","A","B","C","D","E"])
+ sb = builder.ChainContextSubstBuilder(font, None)
+ prefix, input_, suffix, lookups = [["a"], ["b"]], [["c"]], [], [None]
+ sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
+
+ prefix, input_, suffix, lookups = [["a"], ["d"]], [["c"]], [], [None]
+ sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
+
+ sb.add_subtable_break(None)
+
+ # Second subtable has some glyph classes
+ prefix, input_, suffix, lookups = [["A"]], [["E"]], [], [None]
+ sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
+ prefix, input_, suffix, lookups = [["A"]], [["C","D"]], [], [None]
+ sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
+ prefix, input_, suffix, lookups = [["A", "B"]], [["E"]], [], [None]
+ sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
+
+ sb.add_subtable_break(None)
+
+ # Third subtable has no pre/post context
+ prefix, input_, suffix, lookups = [], [["E"]], [], [None]
+ sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
+ prefix, input_, suffix, lookups = [], [["C","D"]], [], [None]
+ sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
+
+ rulesets = sb.rulesets()
+ assert len(rulesets) == 3
+ assert rulesets[0].hasPrefixOrSuffix
+ assert not rulesets[0].hasAnyGlyphClasses
+ cd = rulesets[0].format2ClassDefs()
+ assert set(cd[0].classes()[1:]) == set([("d",),("b",),("a",)])
+ assert set(cd[1].classes()[1:]) == set([("c",)])
+ assert set(cd[2].classes()[1:]) == set()
+
+ assert rulesets[1].hasPrefixOrSuffix
+ assert rulesets[1].hasAnyGlyphClasses
+ assert not rulesets[1].format2ClassDefs()
+
+ assert not rulesets[2].hasPrefixOrSuffix
+ assert rulesets[2].hasAnyGlyphClasses
+ assert rulesets[2].format2ClassDefs()
+ cd = rulesets[2].format2ClassDefs()
+ assert set(cd[0].classes()[1:]) == set()
+ assert set(cd[1].classes()[1:]) == set([("C","D"), ("E",)])
+ assert set(cd[2].classes()[1:]) == set()
+
if __name__ == "__main__":
import sys
diff --git a/Tests/otlLib/data/gpos_91.ttx b/Tests/otlLib/data/gpos_91.ttx
index ee7bf7c2..7befe7b2 100644
--- a/Tests/otlLib/data/gpos_91.ttx
+++ b/Tests/otlLib/data/gpos_91.ttx
@@ -85,7 +85,7 @@
<ExtensionPos index="0" Format="1">
<ExtensionLookupType value="1"/>
<SinglePos Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat value="4"/>
diff --git a/Tests/otlLib/data/gsub_51.ttx b/Tests/otlLib/data/gsub_51.ttx
index 280582c9..0e6a4133 100644
--- a/Tests/otlLib/data/gsub_51.ttx
+++ b/Tests/otlLib/data/gsub_51.ttx
@@ -88,7 +88,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
</SingleSubst>
@@ -97,7 +97,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -105,9 +105,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -117,7 +117,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -126,7 +126,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/otlLib/data/gsub_52.ttx b/Tests/otlLib/data/gsub_52.ttx
index 189178c3..4e83b96d 100644
--- a/Tests/otlLib/data/gsub_52.ttx
+++ b/Tests/otlLib/data/gsub_52.ttx
@@ -88,7 +88,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
</SingleSubst>
@@ -97,7 +97,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -105,9 +105,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -117,7 +117,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -126,10 +126,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
</ClassDef>
<!-- SubClassSetCount=2 -->
diff --git a/Tests/otlLib/data/gsub_71.ttx b/Tests/otlLib/data/gsub_71.ttx
index 201de4cb..acf6cdb6 100644
--- a/Tests/otlLib/data/gsub_71.ttx
+++ b/Tests/otlLib/data/gsub_71.ttx
@@ -87,7 +87,7 @@
<!-- SubTableCount=1 -->
<ExtensionSubst index="0" Format="1">
<ExtensionLookupType value="1"/>
- <SingleSubst Format="1">
+ <SingleSubst>
<Substitution in="g18" out="g23"/>
<Substitution in="g19" out="g24"/>
</SingleSubst>
diff --git a/Tests/otlLib/maxContextCalc_test.py b/Tests/otlLib/maxContextCalc_test.py
index 759b30dd..dc169c60 100644
--- a/Tests/otlLib/maxContextCalc_test.py
+++ b/Tests/otlLib/maxContextCalc_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
import os
import pytest
diff --git a/Tests/otlLib/mock_builder_test.py b/Tests/otlLib/mock_builder_test.py
new file mode 100644
index 00000000..b3fecd83
--- /dev/null
+++ b/Tests/otlLib/mock_builder_test.py
@@ -0,0 +1,86 @@
+from fontTools.otlLib.builder import (
+ AlternateSubstBuilder,
+ ChainContextPosBuilder,
+ ChainContextSubstBuilder,
+ LigatureSubstBuilder,
+ MultipleSubstBuilder,
+ CursivePosBuilder,
+ MarkBasePosBuilder,
+ MarkLigPosBuilder,
+ MarkMarkPosBuilder,
+ ReverseChainSingleSubstBuilder,
+ SingleSubstBuilder,
+ ClassPairPosSubtableBuilder,
+ PairPosBuilder,
+ SinglePosBuilder,
+ ChainContextualRule
+)
+from fontTools.otlLib.error import OpenTypeLibError
+from fontTools.ttLib import TTFont
+from fontTools.misc.loggingTools import CapturingLogHandler
+import logging
+import pytest
+
+
+@pytest.fixture
+def ttfont():
+ glyphs = """
+ .notdef space slash fraction semicolon period comma ampersand
+ quotedblleft quotedblright quoteleft quoteright
+ zero one two three four five six seven eight nine
+ zero.oldstyle one.oldstyle two.oldstyle three.oldstyle
+ four.oldstyle five.oldstyle six.oldstyle seven.oldstyle
+ eight.oldstyle nine.oldstyle onequarter onehalf threequarters
+ onesuperior twosuperior threesuperior ordfeminine ordmasculine
+ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+ a b c d e f g h i j k l m n o p q r s t u v w x y z
+ A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc
+ N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc
+ A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3
+ a.alt1 a.alt2 a.alt3 a.end b.alt c.mid d.alt d.mid
+ e.begin e.mid e.end m.begin n.end s.end z.end
+ Eng Eng.alt1 Eng.alt2 Eng.alt3
+ A.swash B.swash C.swash D.swash E.swash F.swash G.swash H.swash
+ I.swash J.swash K.swash L.swash M.swash N.swash O.swash P.swash
+ Q.swash R.swash S.swash T.swash U.swash V.swash W.swash X.swash
+ Y.swash Z.swash
+ f_l c_h c_k c_s c_t f_f f_f_i f_f_l f_i o_f_f_i s_t f_i.begin
+ a_n_d T_h T_h.swash germandbls ydieresis yacute breve
+ grave acute dieresis macron circumflex cedilla umlaut ogonek caron
+ damma hamza sukun kasratan lam_meem_jeem noon.final noon.initial
+ by feature lookup sub table uni0327 uni0328 e.fina
+ """.split()
+ glyphs.extend("cid{:05d}".format(cid) for cid in range(800, 1001 + 1))
+ font = TTFont()
+ font.setGlyphOrder(glyphs)
+ return font
+
+
+class MockBuilderLocation(object):
+ def __init__(self, location):
+ self.location = location
+
+ def __str__(self):
+ return "%s:%s" % self.location
+
+
+def test_unsupported_subtable_break_1(ttfont):
+ location = MockBuilderLocation((0, "alpha"))
+
+ logger = logging.getLogger("fontTools.otlLib.builder")
+
+ with CapturingLogHandler(logger, "INFO") as captor:
+ builder = SinglePosBuilder(ttfont, location)
+ builder.add_subtable_break(MockBuilderLocation((5, "beta")))
+ builder.build()
+
+ captor.assertRegex('5:beta: unsupported "subtable" statement for lookup type')
+
+def test_chain_pos_references_GSUB_lookup(ttfont):
+ location = MockBuilderLocation((0, "alpha"))
+ builder = ChainContextPosBuilder(ttfont, location)
+ builder2 = SingleSubstBuilder(ttfont, location)
+ builder.rules.append(ChainContextualRule([], [], [], [[builder2]]))
+
+ with pytest.raises(OpenTypeLibError, match="0:alpha: Missing index of the specified lookup, might be a substitution lookup"):
+ builder.build()
diff --git a/Tests/pens/__init__.py b/Tests/pens/__init__.py
new file mode 100644
index 00000000..187b9816
--- /dev/null
+++ b/Tests/pens/__init__.py
@@ -0,0 +1,13 @@
+import os
+from fontTools.ufoLib.glifLib import GlyphSet
+import pkg_resources
+
+DATADIR = os.path.join(os.path.dirname(__file__), 'data')
+CUBIC_GLYPHS = GlyphSet(os.path.join(DATADIR, 'cubic'))
+QUAD_GLYPHS = GlyphSet(os.path.join(DATADIR, 'quadratic'))
+
+import unittest
+# Python 3 renamed 'assertRaisesRegexp' to 'assertRaisesRegex', and fires
+# deprecation warnings if a program uses the old name.
+if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
+ unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
diff --git a/Tests/pens/areaPen_test.py b/Tests/pens/areaPen_test.py
index ae915df5..c3f3f80c 100644
--- a/Tests/pens/areaPen_test.py
+++ b/Tests/pens/areaPen_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.areaPen import AreaPen
import unittest
diff --git a/Tests/pens/basePen_test.py b/Tests/pens/basePen_test.py
index 2a7e43c5..db57e80e 100644
--- a/Tests/pens/basePen_test.py
+++ b/Tests/pens/basePen_test.py
@@ -1,7 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import \
- BasePen, decomposeSuperBezierSegment, decomposeQuadraticSegment
+ AbstractPen, BasePen, decomposeSuperBezierSegment, decomposeQuadraticSegment
+from fontTools.pens.pointPen import AbstractPointPen
from fontTools.misc.loggingTools import CapturingLogHandler
import unittest
diff --git a/Tests/pens/boundsPen_test.py b/Tests/pens/boundsPen_test.py
index 533c5750..c0c56108 100644
--- a/Tests/pens/boundsPen_test.py
+++ b/Tests/pens/boundsPen_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.boundsPen import BoundsPen, ControlBoundsPen
import unittest
diff --git a/Tests/pens/cocoaPen_test.py b/Tests/pens/cocoaPen_test.py
new file mode 100644
index 00000000..11077c0b
--- /dev/null
+++ b/Tests/pens/cocoaPen_test.py
@@ -0,0 +1,58 @@
+import unittest
+
+try:
+ from fontTools.pens.cocoaPen import CocoaPen
+ from AppKit import NSBezierPathElementMoveTo, NSBezierPathElementLineTo
+ from AppKit import NSBezierPathElementCurveTo, NSBezierPathElementClosePath
+
+ PATH_ELEMENTS = {
+ # NSBezierPathElement key desc
+ NSBezierPathElementMoveTo: 'moveto',
+ NSBezierPathElementLineTo: 'lineto',
+ NSBezierPathElementCurveTo: 'curveto',
+ NSBezierPathElementClosePath: 'close',
+ }
+
+ PYOBJC_AVAILABLE = True
+except ImportError:
+ PYOBJC_AVAILABLE = False
+
+
+def draw(pen):
+ pen.moveTo((50, 0))
+ pen.lineTo((50, 500))
+ pen.lineTo((200, 500))
+ pen.curveTo((350, 500), (450, 400), (450, 250))
+ pen.curveTo((450, 100), (350, 0), (200, 0))
+ pen.closePath()
+
+
+def cocoaPathToString(path):
+ num_elements = path.elementCount()
+ output = []
+ for i in range(num_elements - 1):
+ elem_type, elem_points = path.elementAtIndex_associatedPoints_(i)
+ elem_type = PATH_ELEMENTS[elem_type]
+ path_points = " ".join([f"{p.x} {p.y}" for p in elem_points])
+ output.append(f"{elem_type} {path_points}")
+ return " ".join(output)
+
+
+@unittest.skipUnless(PYOBJC_AVAILABLE, "pyobjc not installed")
+class CocoaPenTest(unittest.TestCase):
+ def test_draw(self):
+ pen = CocoaPen(None)
+ draw(pen)
+ self.assertEqual(
+ "moveto 50.0 0.0 lineto 50.0 500.0 lineto 200.0 500.0 curveto 350.0 500.0 450.0 400.0 450.0 250.0 curveto 450.0 100.0 350.0 0.0 200.0 0.0 close ",
+ cocoaPathToString(pen.path)
+ )
+
+ def test_empty(self):
+ pen = CocoaPen(None)
+ self.assertEqual("", cocoaPathToString(pen.path))
+
+
+if __name__ == '__main__':
+ import sys
+ sys.exit(unittest.main())
diff --git a/Tests/pens/cu2quPen_test.py b/Tests/pens/cu2quPen_test.py
new file mode 100644
index 00000000..db517879
--- /dev/null
+++ b/Tests/pens/cu2quPen_test.py
@@ -0,0 +1,390 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+from fontTools.pens.cu2quPen import Cu2QuPen, Cu2QuPointPen
+from . import CUBIC_GLYPHS, QUAD_GLYPHS
+from .utils import DummyGlyph, DummyPointGlyph
+from .utils import DummyPen, DummyPointPen
+from fontTools.misc.loggingTools import CapturingLogHandler
+from textwrap import dedent
+import logging
+
+
+MAX_ERR = 1.0
+
+
+class _TestPenMixin(object):
+ """Collection of tests that are shared by both the SegmentPen and the
+ PointPen test cases, plus some helper methods.
+ """
+
+ maxDiff = None
+
+ def diff(self, expected, actual):
+ import difflib
+ expected = str(self.Glyph(expected)).splitlines(True)
+ actual = str(self.Glyph(actual)).splitlines(True)
+ diff = difflib.unified_diff(
+ expected, actual, fromfile='expected', tofile='actual')
+ return "".join(diff)
+
+ def convert_glyph(self, glyph, **kwargs):
+ # draw source glyph onto a new glyph using a Cu2Qu pen and return it
+ converted = self.Glyph()
+ pen = getattr(converted, self.pen_getter_name)()
+ quadpen = self.Cu2QuPen(pen, MAX_ERR, **kwargs)
+ getattr(glyph, self.draw_method_name)(quadpen)
+ return converted
+
+ def expect_glyph(self, source, expected):
+ converted = self.convert_glyph(source)
+ self.assertNotEqual(converted, source)
+ if not converted.approx(expected):
+ print(self.diff(expected, converted))
+ self.fail("converted glyph is different from expected")
+
+ def test_convert_simple_glyph(self):
+ self.expect_glyph(CUBIC_GLYPHS['a'], QUAD_GLYPHS['a'])
+ self.expect_glyph(CUBIC_GLYPHS['A'], QUAD_GLYPHS['A'])
+
+ def test_convert_composite_glyph(self):
+ source = CUBIC_GLYPHS['Aacute']
+ converted = self.convert_glyph(source)
+ # components don't change after quadratic conversion
+ self.assertEqual(converted, source)
+
+ def test_convert_mixed_glyph(self):
+ # this contains a mix of contours and components
+ self.expect_glyph(CUBIC_GLYPHS['Eacute'], QUAD_GLYPHS['Eacute'])
+
+ def test_reverse_direction(self):
+ for name in ('a', 'A', 'Eacute'):
+ source = CUBIC_GLYPHS[name]
+ normal_glyph = self.convert_glyph(source)
+ reversed_glyph = self.convert_glyph(source, reverse_direction=True)
+
+ # the number of commands is the same, just their order is iverted
+ self.assertTrue(
+ len(normal_glyph.outline), len(reversed_glyph.outline))
+ self.assertNotEqual(normal_glyph, reversed_glyph)
+
+ def test_stats(self):
+ stats = {}
+ for name in CUBIC_GLYPHS.keys():
+ source = CUBIC_GLYPHS[name]
+ self.convert_glyph(source, stats=stats)
+
+ self.assertTrue(stats)
+ self.assertTrue('1' in stats)
+ self.assertEqual(type(stats['1']), int)
+
+ def test_addComponent(self):
+ pen = self.Pen()
+ quadpen = self.Cu2QuPen(pen, MAX_ERR)
+ quadpen.addComponent("a", (1, 2, 3, 4, 5.0, 6.0))
+
+ # components are passed through without changes
+ self.assertEqual(str(pen).splitlines(), [
+ "pen.addComponent('a', (1, 2, 3, 4, 5.0, 6.0))",
+ ])
+
+
+class TestCu2QuPen(unittest.TestCase, _TestPenMixin):
+
+ def __init__(self, *args, **kwargs):
+ super(TestCu2QuPen, self).__init__(*args, **kwargs)
+ self.Glyph = DummyGlyph
+ self.Pen = DummyPen
+ self.Cu2QuPen = Cu2QuPen
+ self.pen_getter_name = 'getPen'
+ self.draw_method_name = 'draw'
+
+ def test__check_contour_is_open(self):
+ msg = "moveTo is required"
+ quadpen = Cu2QuPen(DummyPen(), MAX_ERR)
+
+ with self.assertRaisesRegex(AssertionError, msg):
+ quadpen.lineTo((0, 0))
+ with self.assertRaisesRegex(AssertionError, msg):
+ quadpen.qCurveTo((0, 0), (1, 1))
+ with self.assertRaisesRegex(AssertionError, msg):
+ quadpen.curveTo((0, 0), (1, 1), (2, 2))
+ with self.assertRaisesRegex(AssertionError, msg):
+ quadpen.closePath()
+ with self.assertRaisesRegex(AssertionError, msg):
+ quadpen.endPath()
+
+ quadpen.moveTo((0, 0)) # now it works
+ quadpen.lineTo((1, 1))
+ quadpen.qCurveTo((2, 2), (3, 3))
+ quadpen.curveTo((4, 4), (5, 5), (6, 6))
+ quadpen.closePath()
+
+ def test__check_contour_closed(self):
+ msg = "closePath or endPath is required"
+ quadpen = Cu2QuPen(DummyPen(), MAX_ERR)
+ quadpen.moveTo((0, 0))
+
+ with self.assertRaisesRegex(AssertionError, msg):
+ quadpen.moveTo((1, 1))
+ with self.assertRaisesRegex(AssertionError, msg):
+ quadpen.addComponent("a", (1, 0, 0, 1, 0, 0))
+
+ # it works if contour is closed
+ quadpen.closePath()
+ quadpen.moveTo((1, 1))
+ quadpen.endPath()
+ quadpen.addComponent("a", (1, 0, 0, 1, 0, 0))
+
+ def test_qCurveTo_no_points(self):
+ quadpen = Cu2QuPen(DummyPen(), MAX_ERR)
+ quadpen.moveTo((0, 0))
+
+ with self.assertRaisesRegex(
+ AssertionError, "illegal qcurve segment point count: 0"):
+ quadpen.qCurveTo()
+
+ def test_qCurveTo_1_point(self):
+ pen = DummyPen()
+ quadpen = Cu2QuPen(pen, MAX_ERR)
+ quadpen.moveTo((0, 0))
+ quadpen.qCurveTo((1, 1))
+
+ self.assertEqual(str(pen).splitlines(), [
+ "pen.moveTo((0, 0))",
+ "pen.lineTo((1, 1))",
+ ])
+
+ def test_qCurveTo_more_than_1_point(self):
+ pen = DummyPen()
+ quadpen = Cu2QuPen(pen, MAX_ERR)
+ quadpen.moveTo((0, 0))
+ quadpen.qCurveTo((1, 1), (2, 2))
+
+ self.assertEqual(str(pen).splitlines(), [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((1, 1), (2, 2))",
+ ])
+
+ def test_curveTo_no_points(self):
+ quadpen = Cu2QuPen(DummyPen(), MAX_ERR)
+ quadpen.moveTo((0, 0))
+
+ with self.assertRaisesRegex(
+ AssertionError, "illegal curve segment point count: 0"):
+ quadpen.curveTo()
+
+ def test_curveTo_1_point(self):
+ pen = DummyPen()
+ quadpen = Cu2QuPen(pen, MAX_ERR)
+ quadpen.moveTo((0, 0))
+ quadpen.curveTo((1, 1))
+
+ self.assertEqual(str(pen).splitlines(), [
+ "pen.moveTo((0, 0))",
+ "pen.lineTo((1, 1))",
+ ])
+
+ def test_curveTo_2_points(self):
+ pen = DummyPen()
+ quadpen = Cu2QuPen(pen, MAX_ERR)
+ quadpen.moveTo((0, 0))
+ quadpen.curveTo((1, 1), (2, 2))
+
+ self.assertEqual(str(pen).splitlines(), [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((1, 1), (2, 2))",
+ ])
+
+ def test_curveTo_3_points(self):
+ pen = DummyPen()
+ quadpen = Cu2QuPen(pen, MAX_ERR)
+ quadpen.moveTo((0, 0))
+ quadpen.curveTo((1, 1), (2, 2), (3, 3))
+
+ self.assertEqual(str(pen).splitlines(), [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((0.75, 0.75), (2.25, 2.25), (3, 3))",
+ ])
+
+ def test_curveTo_more_than_3_points(self):
+ # a 'SuperBezier' as described in fontTools.basePen.AbstractPen
+ pen = DummyPen()
+ quadpen = Cu2QuPen(pen, MAX_ERR)
+ quadpen.moveTo((0, 0))
+ quadpen.curveTo((1, 1), (2, 2), (3, 3), (4, 4))
+
+ self.assertEqual(str(pen).splitlines(), [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((0.75, 0.75), (1.625, 1.625), (2, 2))",
+ "pen.qCurveTo((2.375, 2.375), (3.25, 3.25), (4, 4))",
+ ])
+
+ def test_addComponent(self):
+ pen = DummyPen()
+ quadpen = Cu2QuPen(pen, MAX_ERR)
+ quadpen.addComponent("a", (1, 2, 3, 4, 5.0, 6.0))
+
+ # components are passed through without changes
+ self.assertEqual(str(pen).splitlines(), [
+ "pen.addComponent('a', (1, 2, 3, 4, 5.0, 6.0))",
+ ])
+
+ def test_ignore_single_points(self):
+ pen = DummyPen()
+ try:
+ logging.captureWarnings(True)
+ with CapturingLogHandler("py.warnings", level="WARNING") as log:
+ quadpen = Cu2QuPen(pen, MAX_ERR, ignore_single_points=True)
+ finally:
+ logging.captureWarnings(False)
+ quadpen.moveTo((0, 0))
+ quadpen.endPath()
+ quadpen.moveTo((1, 1))
+ quadpen.closePath()
+
+ self.assertGreaterEqual(len(log.records), 1)
+ self.assertIn("ignore_single_points is deprecated",
+ log.records[0].args[0])
+
+ # single-point contours were ignored, so the pen commands are empty
+ self.assertFalse(pen.commands)
+
+ # redraw without ignoring single points
+ quadpen.ignore_single_points = False
+ quadpen.moveTo((0, 0))
+ quadpen.endPath()
+ quadpen.moveTo((1, 1))
+ quadpen.closePath()
+
+ self.assertTrue(pen.commands)
+ self.assertEqual(str(pen).splitlines(), [
+ "pen.moveTo((0, 0))",
+ "pen.endPath()",
+ "pen.moveTo((1, 1))",
+ "pen.closePath()"
+ ])
+
+
+class TestCu2QuPointPen(unittest.TestCase, _TestPenMixin):
+
+ def __init__(self, *args, **kwargs):
+ super(TestCu2QuPointPen, self).__init__(*args, **kwargs)
+ self.Glyph = DummyPointGlyph
+ self.Pen = DummyPointPen
+ self.Cu2QuPen = Cu2QuPointPen
+ self.pen_getter_name = 'getPointPen'
+ self.draw_method_name = 'drawPoints'
+
+ def test_super_bezier_curve(self):
+ pen = DummyPointPen()
+ quadpen = Cu2QuPointPen(pen, MAX_ERR)
+ quadpen.beginPath()
+ quadpen.addPoint((0, 0), segmentType="move")
+ quadpen.addPoint((1, 1))
+ quadpen.addPoint((2, 2))
+ quadpen.addPoint((3, 3))
+ quadpen.addPoint(
+ (4, 4), segmentType="curve", smooth=False, name="up", selected=1)
+ quadpen.endPath()
+
+ self.assertEqual(str(pen).splitlines(), """\
+pen.beginPath()
+pen.addPoint((0, 0), name=None, segmentType='move', smooth=False)
+pen.addPoint((0.75, 0.75), name=None, segmentType=None, smooth=False)
+pen.addPoint((1.625, 1.625), name=None, segmentType=None, smooth=False)
+pen.addPoint((2, 2), name=None, segmentType='qcurve', smooth=True)
+pen.addPoint((2.375, 2.375), name=None, segmentType=None, smooth=False)
+pen.addPoint((3.25, 3.25), name=None, segmentType=None, smooth=False)
+pen.addPoint((4, 4), name='up', segmentType='qcurve', selected=1, smooth=False)
+pen.endPath()""".splitlines())
+
+ def test__flushContour_restore_starting_point(self):
+ pen = DummyPointPen()
+ quadpen = Cu2QuPointPen(pen, MAX_ERR)
+
+ # collect the output of _flushContour before it's sent to _drawPoints
+ new_segments = []
+ def _drawPoints(segments):
+ new_segments.extend(segments)
+ Cu2QuPointPen._drawPoints(quadpen, segments)
+ quadpen._drawPoints = _drawPoints
+
+ # a closed path (ie. no "move" segmentType)
+ quadpen._flushContour([
+ ("curve", [
+ ((2, 2), False, None, {}),
+ ((1, 1), False, None, {}),
+ ((0, 0), False, None, {}),
+ ]),
+ ("curve", [
+ ((1, 1), False, None, {}),
+ ((2, 2), False, None, {}),
+ ((3, 3), False, None, {}),
+ ]),
+ ])
+
+ # the original starting point is restored: the last segment has become
+ # the first
+ self.assertEqual(new_segments[0][1][-1][0], (3, 3))
+ self.assertEqual(new_segments[-1][1][-1][0], (0, 0))
+
+ new_segments = []
+ # an open path (ie. starting with "move")
+ quadpen._flushContour([
+ ("move", [
+ ((0, 0), False, None, {}),
+ ]),
+ ("curve", [
+ ((1, 1), False, None, {}),
+ ((2, 2), False, None, {}),
+ ((3, 3), False, None, {}),
+ ]),
+ ])
+
+ # the segment order stays the same before and after _flushContour
+ self.assertEqual(new_segments[0][1][-1][0], (0, 0))
+ self.assertEqual(new_segments[-1][1][-1][0], (3, 3))
+
+ def test_quad_no_oncurve(self):
+ """When passed a contour which has no on-curve points, the
+ Cu2QuPointPen will treat it as a special quadratic contour whose
+ first point has 'None' coordinates.
+ """
+ self.maxDiff = None
+ pen = DummyPointPen()
+ quadpen = Cu2QuPointPen(pen, MAX_ERR)
+ quadpen.beginPath()
+ quadpen.addPoint((1, 1))
+ quadpen.addPoint((2, 2))
+ quadpen.addPoint((3, 3))
+ quadpen.endPath()
+
+ self.assertEqual(
+ str(pen),
+ dedent(
+ """\
+ pen.beginPath()
+ pen.addPoint((1, 1), name=None, segmentType=None, smooth=False)
+ pen.addPoint((2, 2), name=None, segmentType=None, smooth=False)
+ pen.addPoint((3, 3), name=None, segmentType=None, smooth=False)
+ pen.endPath()"""
+ )
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Tests/pens/data/cubic/A_.glif b/Tests/pens/data/cubic/A_.glif
new file mode 100755
index 00000000..bee6cd7f
--- /dev/null
+++ b/Tests/pens/data/cubic/A_.glif
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="A" format="1">
+ <unicode hex="0041"/>
+ <advance width="668"/>
+ <outline>
+ <contour>
+ <point x="501" y="347" type="line"/>
+ <point x="412" y="393"/>
+ <point x="359" y="401"/>
+ <point x="282" y="401" type="curve" smooth="yes"/>
+ <point x="192" y="401"/>
+ <point x="131" y="390"/>
+ <point x="114" y="347" type="curve"/>
+ <point x="119" y="347"/>
+ <point x="127" y="330"/>
+ <point x="127" y="316" type="curve" smooth="yes"/>
+ <point x="127" y="292"/>
+ <point x="102" y="277"/>
+ <point x="71" y="277" type="curve" smooth="yes"/>
+ <point x="37" y="277"/>
+ <point x="9" y="296"/>
+ <point x="9" y="335" type="curve" smooth="yes"/>
+ <point x="9" y="403"/>
+ <point x="95" y="458"/>
+ <point x="213" y="458" type="curve" smooth="yes"/>
+ <point x="278" y="458"/>
+ <point x="428" y="439"/>
+ <point x="526" y="385" type="curve"/>
+ </contour>
+ <contour>
+ <point x="629" y="678" type="line"/>
+ <point x="615" y="685"/>
+ <point x="596" y="692"/>
+ <point x="578" y="692" type="curve" smooth="yes"/>
+ <point x="421" y="692"/>
+ <point x="204" y="149"/>
+ <point x="204" y="-58" type="curve" smooth="yes"/>
+ <point x="204" y="-90"/>
+ <point x="212" y="-105"/>
+ <point x="218" y="-114" type="curve"/>
+ <point x="164" y="-114"/>
+ <point x="84" y="-112"/>
+ <point x="84" y="-8" type="curve" smooth="yes"/>
+ <point x="84" y="191"/>
+ <point x="374" y="750"/>
+ <point x="613" y="750" type="curve" smooth="yes"/>
+ <point x="636" y="750"/>
+ <point x="668" y="746"/>
+ <point x="692" y="736" type="curve"/>
+ </contour>
+ <contour>
+ <point x="391" y="0" type="line"/>
+ <point x="547" y="736" type="line"/>
+ <point x="692" y="736" type="line"/>
+ <point x="535" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph> \ No newline at end of file
diff --git a/Tests/pens/data/cubic/A_acute.glif b/Tests/pens/data/cubic/A_acute.glif
new file mode 100755
index 00000000..72a88557
--- /dev/null
+++ b/Tests/pens/data/cubic/A_acute.glif
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="Aacute" format="1">
+ <unicode hex="00C1"/>
+ <advance width="668"/>
+ <outline>
+ <component base="A"/>
+ <component base="acute" xOffset="366" yOffset="250"/>
+ </outline>
+</glyph> \ No newline at end of file
diff --git a/Tests/pens/data/cubic/E_acute.glif b/Tests/pens/data/cubic/E_acute.glif
new file mode 100755
index 00000000..fa57e34e
--- /dev/null
+++ b/Tests/pens/data/cubic/E_acute.glif
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="E" format="1">
+ <unicode hex="0045"/>
+ <advance width="459"/>
+ <outline>
+ <contour>
+ <point x="461" y="180" type="line"/>
+ <point x="425" y="114"/>
+ <point x="337" y="78"/>
+ <point x="276" y="78" type="curve" smooth="yes"/>
+ <point x="208" y="78"/>
+ <point x="181" y="123"/>
+ <point x="181" y="179" type="curve" smooth="yes"/>
+ <point x="181" y="271"/>
+ <point x="253" y="394"/>
+ <point x="360" y="418" type="curve"/>
+ <point x="288" y="437"/>
+ <point x="259" y="494"/>
+ <point x="259" y="552" type="curve" smooth="yes"/>
+ <point x="259" y="622"/>
+ <point x="302" y="693"/>
+ <point x="361" y="693" type="curve" smooth="yes"/>
+ <point x="388" y="693"/>
+ <point x="416" y="678"/>
+ <point x="416" y="637" type="curve" smooth="yes"/>
+ <point x="416" y="603"/>
+ <point x="397" y="558"/>
+ <point x="372" y="550" type="curve"/>
+ <point x="386" y="531"/>
+ <point x="402" y="523"/>
+ <point x="419" y="523" type="curve" smooth="yes"/>
+ <point x="459" y="523"/>
+ <point x="493" y="567"/>
+ <point x="493" y="627" type="curve" smooth="yes"/>
+ <point x="493" y="709"/>
+ <point x="429" y="752"/>
+ <point x="350" y="752" type="curve" smooth="yes"/>
+ <point x="222" y="752"/>
+ <point x="131" y="640"/>
+ <point x="131" y="537" type="curve" smooth="yes"/>
+ <point x="131" y="492"/>
+ <point x="149" y="448"/>
+ <point x="192" y="417" type="curve"/>
+ <point x="77" y="389"/>
+ <point x="7" y="263"/>
+ <point x="7" y="158" type="curve" smooth="yes"/>
+ <point x="7" y="64"/>
+ <point x="63" y="-18"/>
+ <point x="191" y="-18" type="curve" smooth="yes"/>
+ <point x="310" y="-18"/>
+ <point x="432" y="52"/>
+ <point x="484" y="170" type="curve"/>
+ </contour>
+ <component base="acute" xOffset="210" yOffset="250"/>
+ </outline>
+</glyph> \ No newline at end of file
diff --git a/Tests/pens/data/cubic/a.glif b/Tests/pens/data/cubic/a.glif
new file mode 100644
index 00000000..1fc63b4f
--- /dev/null
+++ b/Tests/pens/data/cubic/a.glif
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="a" format="1">
+ <unicode hex="0061"/>
+ <advance width="1118"/>
+ <outline>
+ <contour>
+ <point x="771" y="183" type="line"/>
+ <point x="771" y="123"/>
+ <point x="782" y="41"/>
+ <point x="802" y="0" type="curve"/>
+ <point x="1010" y="0" type="line"/>
+ <point x="1010" y="17" type="line"/>
+ <point x="984" y="76"/>
+ <point x="971" y="166"/>
+ <point x="971" y="238" type="curve" smooth="yes"/>
+ <point x="971" y="738" type="line"/>
+ <point x="971" y="981"/>
+ <point x="803" y="1102"/>
+ <point x="566" y="1102" type="curve" smooth="yes"/>
+ <point x="301" y="1102"/>
+ <point x="133" y="935"/>
+ <point x="133" y="782" type="curve"/>
+ <point x="333" y="782" type="line"/>
+ <point x="333" y="867"/>
+ <point x="421" y="945"/>
+ <point x="554" y="945" type="curve" smooth="yes"/>
+ <point x="697" y="945"/>
+ <point x="771" y="864"/>
+ <point x="771" y="740" type="curve"/>
+ </contour>
+ <contour>
+ <point x="804" y="661" type="line"/>
+ <point x="596" y="661" type="line"/>
+ <point x="301" y="661"/>
+ <point x="111" y="539"/>
+ <point x="111" y="303" type="curve" smooth="yes"/>
+ <point x="111" y="123"/>
+ <point x="257" y="-20"/>
+ <point x="480" y="-20" type="curve" smooth="yes"/>
+ <point x="711" y="-20"/>
+ <point x="857" y="170"/>
+ <point x="874" y="277" type="curve"/>
+ <point x="789" y="370" type="line"/>
+ <point x="789" y="279"/>
+ <point x="673" y="151"/>
+ <point x="510" y="151" type="curve" smooth="yes"/>
+ <point x="377" y="151"/>
+ <point x="311" y="230"/>
+ <point x="311" y="331" type="curve" smooth="yes"/>
+ <point x="311" y="462"/>
+ <point x="425" y="524"/>
+ <point x="629" y="524" type="curve"/>
+ <point x="806" y="524" type="line"/>
+ </contour>
+ <contour>
+ <point name="top" x="582" y="1290" type="move"/>
+ </contour>
+ <contour>
+ <point name="bottom" x="501" y="0" type="move"/>
+ </contour>
+ <contour>
+ <point name="ogonek" x="974" y="0" type="move"/>
+ </contour>
+ <contour>
+ <point name="rhalfring" x="700" y="1290" type="move"/>
+ </contour>
+ <contour>
+ <point name="top_dd" x="1118" y="1600" type="move"/>
+ </contour>
+ <contour>
+ <point name="bottom_dd" x="1118" y="-407" type="move"/>
+ </contour>
+ <contour>
+ <point name="rhotichook" x="879" y="654" type="move"/>
+ </contour>
+ <contour>
+ <point name="top0315" x="1118" y="1290" type="move"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/pens/data/cubic/acute.glif b/Tests/pens/data/cubic/acute.glif
new file mode 100755
index 00000000..2d392514
--- /dev/null
+++ b/Tests/pens/data/cubic/acute.glif
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="acute" format="1">
+ <unicode hex="00B4"/>
+ <advance width="316"/>
+ <outline>
+ <contour>
+ <point x="120" y="561" type="line"/>
+ <point x="195" y="561" type="line"/>
+ <point x="316" y="750" type="line"/>
+ <point x="211" y="750" type="line"/>
+ </contour>
+ </outline>
+</glyph> \ No newline at end of file
diff --git a/Tests/pens/data/cubic/contents.plist b/Tests/pens/data/cubic/contents.plist
new file mode 100644
index 00000000..3baa4e79
--- /dev/null
+++ b/Tests/pens/data/cubic/contents.plist
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>A</key>
+ <string>A_.glif</string>
+ <key>Aacute</key>
+ <string>A_acute.glif</string>
+ <key>Eacute</key>
+ <string>E_acute.glif</string>
+ <key>a</key>
+ <string>a.glif</string>
+ <key>acute</key>
+ <string>acute.glif</string>
+</dict>
+</plist>
diff --git a/Tests/pens/data/quadratic/A_.glif b/Tests/pens/data/quadratic/A_.glif
new file mode 100644
index 00000000..dbc10e94
--- /dev/null
+++ b/Tests/pens/data/quadratic/A_.glif
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="A" format="1">
+ <unicode hex="0041"/>
+ <advance width="668"/>
+ <outline>
+ <contour>
+ <point x="501" y="347" type="line"/>
+ <point x="456.5" y="370"/>
+ <point x="386.5" y="393.16666666666663"/>
+ <point x="320.5" y="401"/>
+ <point x="282" y="401" type="qcurve" smooth="yes"/>
+ <point x="214.5" y="401"/>
+ <point x="126.75" y="379.25"/>
+ <point x="114" y="347" type="qcurve"/>
+ <point x="117.75" y="347"/>
+ <point x="127" y="326.5"/>
+ <point x="127" y="316" type="qcurve" smooth="yes"/>
+ <point x="127" y="298"/>
+ <point x="94.25" y="277"/>
+ <point x="71" y="277" type="qcurve" smooth="yes"/>
+ <point x="45.5" y="277"/>
+ <point x="9" y="305.75"/>
+ <point x="9" y="335" type="qcurve" smooth="yes"/>
+ <point x="9" y="369"/>
+ <point x="61.83333333333332" y="424.8333333333333"/>
+ <point x="154" y="458"/>
+ <point x="213" y="458" type="qcurve" smooth="yes"/>
+ <point x="237.375" y="458"/>
+ <point x="313.0052083333333" y="450.2916666666667"/>
+ <point x="401.2447916666667" y="433.2083333333333"/>
+ <point x="489.25" y="405.25"/>
+ <point x="526" y="385" type="qcurve"/>
+ </contour>
+ <contour>
+ <point x="629" y="678" type="line"/>
+ <point x="618.5" y="683.25"/>
+ <point x="591.5" y="692"/>
+ <point x="578" y="692" type="qcurve" smooth="yes"/>
+ <point x="544.3571428571429" y="692"/>
+ <point x="471.67614188532553" y="631.7033527696792"/>
+ <point x="398.9164237123421" y="527.9810495626824"/>
+ <point x="330.9234693877551" y="396.2091836734695"/>
+ <point x="272.54275996112733" y="251.76384839650154"/>
+ <point x="228.61977648202145" y="110.0211370262391"/>
+ <point x="204.00000000000006" y="-13.64285714285704"/>
+ <point x="204" y="-58" type="qcurve" smooth="yes"/>
+ <point x="204" y="-82"/>
+ <point x="213.5" y="-107.25"/>
+ <point x="218" y="-114" type="qcurve"/>
+ <point x="197.75" y="-114"/>
+ <point x="151.36458333333334" y="-109.60416666666667"/>
+ <point x="110.13541666666667" y="-90.39583333333333"/>
+ <point x="84" y="-47"/>
+ <point x="84" y="-8" type="qcurve" smooth="yes"/>
+ <point x="84" y="34.64285714285714"/>
+ <point x="117.10762876579204" y="157.5352283770651"/>
+ <point x="176.7779397473275" y="300.3955296404276"/>
+ <point x="257.04591836734687" y="447.1479591836734"/>
+ <point x="351.94655004859084" y="581.7167152575314"/>
+ <point x="455.5148202137998" y="688.0259961127308"/>
+ <point x="561.7857142857142" y="750"/>
+ <point x="613" y="750" type="qcurve" smooth="yes"/>
+ <point x="630.25" y="750"/>
+ <point x="674" y="743.5"/>
+ <point x="692" y="736" type="qcurve"/>
+ </contour>
+ <contour>
+ <point x="391" y="0" type="line"/>
+ <point x="547" y="736" type="line"/>
+ <point x="692" y="736" type="line"/>
+ <point x="535" y="0" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/pens/data/quadratic/E_acute.glif b/Tests/pens/data/quadratic/E_acute.glif
new file mode 100644
index 00000000..89b4a5af
--- /dev/null
+++ b/Tests/pens/data/quadratic/E_acute.glif
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="E" format="1">
+ <unicode hex="0045"/>
+ <advance width="459"/>
+ <outline>
+ <contour>
+ <point x="461" y="180" type="line"/>
+ <point x="443" y="147"/>
+ <point x="378.91666666666663" y="101.49999999999999"/>
+ <point x="306.5" y="78"/>
+ <point x="276" y="78" type="qcurve" smooth="yes"/>
+ <point x="225" y="78"/>
+ <point x="181" y="137"/>
+ <point x="181" y="179" type="qcurve" smooth="yes"/>
+ <point x="181" y="213.5"/>
+ <point x="206.65104166666666" y="289.3854166666667"/>
+ <point x="254.09895833333334" y="358.6145833333333"/>
+ <point x="319.875" y="409"/>
+ <point x="360" y="418" type="qcurve"/>
+ <point x="306" y="432.25"/>
+ <point x="259" y="508.5"/>
+ <point x="259" y="552" type="qcurve" smooth="yes"/>
+ <point x="259" y="587"/>
+ <point x="285.41666666666663" y="651.6666666666667"/>
+ <point x="331.5" y="693"/>
+ <point x="361" y="693" type="qcurve" smooth="yes"/>
+ <point x="381.25" y="693"/>
+ <point x="416" y="667.75"/>
+ <point x="416" y="637" type="qcurve" smooth="yes"/>
+ <point x="416" y="611.5"/>
+ <point x="390.75" y="556"/>
+ <point x="372" y="550" type="qcurve"/>
+ <point x="391.89473684210526" y="523"/>
+ <point x="419" y="523" type="qcurve" smooth="yes"/>
+ <point x="449" y="523"/>
+ <point x="493" y="582"/>
+ <point x="493" y="627" type="qcurve" smooth="yes"/>
+ <point x="493" y="688.5"/>
+ <point x="409.25" y="752"/>
+ <point x="350" y="752" type="qcurve" smooth="yes"/>
+ <point x="302" y="752"/>
+ <point x="221.84375" y="714.4114583333334"/>
+ <point x="163.15625" y="651.8385416666666"/>
+ <point x="131" y="575.625"/>
+ <point x="131" y="537" type="qcurve" smooth="yes"/>
+ <point x="131" y="503.25"/>
+ <point x="159.75" y="440.25"/>
+ <point x="192" y="417" type="qcurve"/>
+ <point x="134.5" y="403"/>
+ <point x="51.58333333333333" y="319.58333333333326"/>
+ <point x="7" y="210.5"/>
+ <point x="7" y="158" type="qcurve" smooth="yes"/>
+ <point x="7" y="111"/>
+ <point x="45.66666666666667" y="30.83333333333333"/>
+ <point x="127.00000000000001" y="-18"/>
+ <point x="191" y="-18" type="qcurve" smooth="yes"/>
+ <point x="250.5" y="-18"/>
+ <point x="365.4166666666667" y="26.833333333333336"/>
+ <point x="458" y="110.99999999999999"/>
+ <point x="484" y="170" type="qcurve"/>
+ </contour>
+ <component base="acute" xOffset="210" yOffset="250"/>
+ </outline>
+</glyph>
diff --git a/Tests/pens/data/quadratic/a.glif b/Tests/pens/data/quadratic/a.glif
new file mode 100644
index 00000000..dc776e11
--- /dev/null
+++ b/Tests/pens/data/quadratic/a.glif
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="a" format="1">
+ <unicode hex="0061"/>
+ <advance width="1118"/>
+ <outline>
+ <contour>
+ <point x="771" y="183" type="line"/>
+ <point x="771" y="153"/>
+ <point x="778.1666666666665" y="83.58333333333333"/>
+ <point x="792" y="20.5"/>
+ <point x="802" y="0" type="qcurve"/>
+ <point x="1010" y="0" type="line"/>
+ <point x="1010" y="17" type="line"/>
+ <point x="990.5" y="61.25"/>
+ <point x="971" y="184"/>
+ <point x="971" y="238" type="qcurve" smooth="yes"/>
+ <point x="971" y="738" type="line"/>
+ <point x="971" y="859.5"/>
+ <point x="867.25" y="1021.25"/>
+ <point x="684.5" y="1102"/>
+ <point x="566" y="1102" type="qcurve" smooth="yes"/>
+ <point x="466.625" y="1102"/>
+ <point x="306.8385416666667" y="1045.9739583333333"/>
+ <point x="193.41145833333334" y="952.7760416666666"/>
+ <point x="133" y="839.375"/>
+ <point x="133" y="782" type="qcurve"/>
+ <point x="333" y="782" type="line"/>
+ <point x="333" y="824.5"/>
+ <point x="388.0833333333333" y="898.9166666666665"/>
+ <point x="487.5" y="945"/>
+ <point x="554" y="945" type="qcurve" smooth="yes"/>
+ <point x="661.25" y="945"/>
+ <point x="771" y="833"/>
+ <point x="771" y="740" type="qcurve"/>
+ </contour>
+ <contour>
+ <point x="804" y="661" type="line"/>
+ <point x="596" y="661" type="line"/>
+ <point x="448.5" y="661"/>
+ <point x="230.58333333333331" y="580.3333333333334"/>
+ <point x="111" y="421"/>
+ <point x="111" y="303" type="qcurve" smooth="yes"/>
+ <point x="111" y="213"/>
+ <point x="202.58333333333334" y="66.5"/>
+ <point x="368.5" y="-20"/>
+ <point x="480" y="-20" type="qcurve" smooth="yes"/>
+ <point x="549.3000000000001" y="-20"/>
+ <point x="666.664" y="20.413000000000004"/>
+ <point x="760.4599999999999" y="86.77000000000001"/>
+ <point x="828.576" y="165.967"/>
+ <point x="868.8999999999999" y="244.90000000000003"/>
+ <point x="874" y="277" type="qcurve"/>
+ <point x="789" y="370" type="line"/>
+ <point x="789" y="335.875"/>
+ <point x="748.015625" y="259.765625"/>
+ <point x="673.234375" y="192.984375"/>
+ <point x="571.125" y="151"/>
+ <point x="510" y="151" type="qcurve" smooth="yes"/>
+ <point x="443.5" y="151"/>
+ <point x="355.08333333333326" y="198.916666666666666"/>
+ <point x="311" y="280.5"/>
+ <point x="311" y="331" type="qcurve" smooth="yes"/>
+ <point x="311" y="429.25"/>
+ <point x="476" y="524"/>
+ <point x="629" y="524" type="qcurve"/>
+ <point x="806" y="524" type="line"/>
+ </contour>
+ <contour>
+ <point name="top" x="582" y="1290" type="move"/>
+ </contour>
+ <contour>
+ <point name="bottom" x="501" y="0" type="move"/>
+ </contour>
+ <contour>
+ <point name="ogonek" x="974" y="0" type="move"/>
+ </contour>
+ <contour>
+ <point name="rhalfring" x="700" y="1290" type="move"/>
+ </contour>
+ <contour>
+ <point name="top_dd" x="1118" y="1600" type="move"/>
+ </contour>
+ <contour>
+ <point name="bottom_dd" x="1118" y="-407" type="move"/>
+ </contour>
+ <contour>
+ <point name="rhotichook" x="879" y="654" type="move"/>
+ </contour>
+ <contour>
+ <point name="top0315" x="1118" y="1290" type="move"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/pens/data/quadratic/acute.glif b/Tests/pens/data/quadratic/acute.glif
new file mode 100755
index 00000000..2d392514
--- /dev/null
+++ b/Tests/pens/data/quadratic/acute.glif
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="acute" format="1">
+ <unicode hex="00B4"/>
+ <advance width="316"/>
+ <outline>
+ <contour>
+ <point x="120" y="561" type="line"/>
+ <point x="195" y="561" type="line"/>
+ <point x="316" y="750" type="line"/>
+ <point x="211" y="750" type="line"/>
+ </contour>
+ </outline>
+</glyph> \ No newline at end of file
diff --git a/Tests/pens/data/quadratic/contents.plist b/Tests/pens/data/quadratic/contents.plist
new file mode 100644
index 00000000..5ed74ed0
--- /dev/null
+++ b/Tests/pens/data/quadratic/contents.plist
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>A</key>
+ <string>A_.glif</string>
+ <key>Eacute</key>
+ <string>E_acute.glif</string>
+ <key>a</key>
+ <string>a.glif</string>
+ <key>acute</key>
+ <string>acute.glif</string>
+</dict>
+</plist>
diff --git a/Tests/pens/hashPointPen_test.py b/Tests/pens/hashPointPen_test.py
new file mode 100644
index 00000000..6b744e66
--- /dev/null
+++ b/Tests/pens/hashPointPen_test.py
@@ -0,0 +1,138 @@
+from fontTools.misc.transform import Identity
+from fontTools.pens.hashPointPen import HashPointPen
+import pytest
+
+
+class _TestGlyph(object):
+ width = 500
+
+ def drawPoints(self, pen):
+ pen.beginPath(identifier="abc")
+ pen.addPoint((0.0, 0.0), "line", False, "start", identifier="0000")
+ pen.addPoint((10, 110), "line", False, None, identifier="0001")
+ pen.addPoint((50.0, 75.0), None, False, None, identifier="0002")
+ pen.addPoint((60.0, 50.0), None, False, None, identifier="0003")
+ pen.addPoint((50.0, 0.0), "curve", True, "last", identifier="0004")
+ pen.endPath()
+
+
+class _TestGlyph2(_TestGlyph):
+ def drawPoints(self, pen):
+ pen.beginPath(identifier="abc")
+ pen.addPoint((0.0, 0.0), "line", False, "start", identifier="0000")
+ # Minor difference to _TestGlyph() is in the next line:
+ pen.addPoint((101, 10), "line", False, None, identifier="0001")
+ pen.addPoint((50.0, 75.0), None, False, None, identifier="0002")
+ pen.addPoint((60.0, 50.0), None, False, None, identifier="0003")
+ pen.addPoint((50.0, 0.0), "curve", True, "last", identifier="0004")
+ pen.endPath()
+
+
+class _TestGlyph3(_TestGlyph):
+ def drawPoints(self, pen):
+ pen.beginPath(identifier="abc")
+ pen.addPoint((0.0, 0.0), "line", False, "start", identifier="0000")
+ pen.addPoint((10, 110), "line", False, None, identifier="0001")
+ pen.endPath()
+ # Same segment, but in a different path:
+ pen.beginPath(identifier="pth2")
+ pen.addPoint((50.0, 75.0), None, False, None, identifier="0002")
+ pen.addPoint((60.0, 50.0), None, False, None, identifier="0003")
+ pen.addPoint((50.0, 0.0), "curve", True, "last", identifier="0004")
+ pen.endPath()
+
+
+class _TestGlyph4(_TestGlyph):
+ def drawPoints(self, pen):
+ pen.beginPath(identifier="abc")
+ pen.addPoint((0.0, 0.0), "move", False, "start", identifier="0000")
+ pen.addPoint((10, 110), "line", False, None, identifier="0001")
+ pen.addPoint((50.0, 75.0), None, False, None, identifier="0002")
+ pen.addPoint((60.0, 50.0), None, False, None, identifier="0003")
+ pen.addPoint((50.0, 0.0), "curve", True, "last", identifier="0004")
+ pen.endPath()
+
+
+class _TestGlyph5(_TestGlyph):
+ def drawPoints(self, pen):
+ pen.addComponent("b", Identity)
+
+
+class HashPointPenTest(object):
+ def test_addComponent(self):
+ pen = HashPointPen(_TestGlyph().width, {"a": _TestGlyph()})
+ pen.addComponent("a", (2, 0, 0, 3, -10, 5))
+ assert pen.hash == "w500[l0+0l10+110o50+75o60+50c50+0|(+2+0+0+3-10+5)]"
+
+ def test_NestedComponents(self):
+ pen = HashPointPen(
+ _TestGlyph().width, {"a": _TestGlyph5(), "b": _TestGlyph()}
+ ) # "a" contains "b" as a component
+ pen.addComponent("a", (2, 0, 0, 3, -10, 5))
+
+ assert (
+ pen.hash
+ == "w500[[l0+0l10+110o50+75o60+50c50+0|(+1+0+0+1+0+0)](+2+0+0+3-10+5)]"
+ )
+
+ def test_outlineAndComponent(self):
+ pen = HashPointPen(_TestGlyph().width, {"a": _TestGlyph()})
+ glyph = _TestGlyph()
+ glyph.drawPoints(pen)
+ pen.addComponent("a", (2, 0, 0, 2, -10, 5))
+
+ assert (
+ pen.hash
+ == "w500l0+0l10+110o50+75o60+50c50+0|[l0+0l10+110o50+75o60+50c50+0|(+2+0+0+2-10+5)]"
+ )
+
+ def test_addComponent_missing_raises(self):
+ pen = HashPointPen(_TestGlyph().width, dict())
+ with pytest.raises(KeyError) as excinfo:
+ pen.addComponent("a", Identity)
+ assert excinfo.value.args[0] == "a"
+
+ def test_similarGlyphs(self):
+ pen = HashPointPen(_TestGlyph().width)
+ glyph = _TestGlyph()
+ glyph.drawPoints(pen)
+
+ pen2 = HashPointPen(_TestGlyph2().width)
+ glyph = _TestGlyph2()
+ glyph.drawPoints(pen2)
+
+ assert pen.hash != pen2.hash
+
+ def test_similarGlyphs2(self):
+ pen = HashPointPen(_TestGlyph().width)
+ glyph = _TestGlyph()
+ glyph.drawPoints(pen)
+
+ pen2 = HashPointPen(_TestGlyph3().width)
+ glyph = _TestGlyph3()
+ glyph.drawPoints(pen2)
+
+ assert pen.hash != pen2.hash
+
+ def test_similarGlyphs3(self):
+ pen = HashPointPen(_TestGlyph().width)
+ glyph = _TestGlyph()
+ glyph.drawPoints(pen)
+
+ pen2 = HashPointPen(_TestGlyph4().width)
+ glyph = _TestGlyph4()
+ glyph.drawPoints(pen2)
+
+ assert pen.hash != pen2.hash
+
+ def test_glyphVsComposite(self):
+ # If a glyph contains a component, the decomposed glyph should still
+ # compare false
+ pen = HashPointPen(_TestGlyph().width, {"a": _TestGlyph()})
+ pen.addComponent("a", Identity)
+
+ pen2 = HashPointPen(_TestGlyph().width)
+ glyph = _TestGlyph()
+ glyph.drawPoints(pen2)
+
+ assert pen.hash != pen2.hash
diff --git a/Tests/pens/perimeterPen_test.py b/Tests/pens/perimeterPen_test.py
index aa73c3dd..1b645345 100644
--- a/Tests/pens/perimeterPen_test.py
+++ b/Tests/pens/perimeterPen_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.perimeterPen import PerimeterPen
import unittest
diff --git a/Tests/pens/pointInsidePen_test.py b/Tests/pens/pointInsidePen_test.py
index 3696ece3..b561c43f 100644
--- a/Tests/pens/pointInsidePen_test.py
+++ b/Tests/pens/pointInsidePen_test.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from io import StringIO
from fontTools.pens.pointInsidePen import PointInsidePen
import unittest
@@ -73,16 +72,16 @@ class PointInsidePenTest(unittest.TestCase):
@staticmethod
def render(draw_function, even_odd):
- result = BytesIO()
+ result = StringIO()
for y in range(5):
for x in range(10):
pen = PointInsidePen(None, (x + 0.5, y + 0.5), even_odd)
draw_function(pen)
if pen.getResult():
- result.write(b"*")
+ result.write("*")
else:
- result.write(b" ")
- return tounicode(result.getvalue())
+ result.write(" ")
+ return result.getvalue()
def test_contour_no_solutions(self):
diff --git a/Tests/pens/pointPen_test.py b/Tests/pens/pointPen_test.py
index 9c71c5e2..a9201780 100644
--- a/Tests/pens/pointPen_test.py
+++ b/Tests/pens/pointPen_test.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.misc.loggingTools import CapturingLogHandler
import unittest
from fontTools.pens.basePen import AbstractPen
@@ -44,7 +41,7 @@ def _reprKwargs(kwargs):
items = []
for key in sorted(kwargs):
value = kwargs[key]
- if isinstance(value, basestring):
+ if isinstance(value, str):
items.append("%s='%s'" % (key, value))
else:
items.append("%s=%s" % (key, value))
@@ -157,6 +154,67 @@ class PointToSegmentPenTest(unittest.TestCase):
"addPoint((20, 20)) addPoint((20, 40), segmentType='curve') endPath()",
repr(tpen))
+ def test_closed_outputImpliedClosingLine(self):
+ tpen = _TestSegmentPen()
+ ppen = PointToSegmentPen(tpen, outputImpliedClosingLine=True)
+ ppen.beginPath()
+ ppen.addPoint((10, 10), "line")
+ ppen.addPoint((10, 20), "line")
+ ppen.addPoint((20, 20), "line")
+ ppen.endPath()
+ self.assertEqual(
+ "10 10 moveto "
+ "10 20 lineto "
+ "20 20 lineto "
+ "10 10 lineto " # explicit closing line
+ "closepath",
+ repr(tpen)
+ )
+
+ def test_closed_line_overlapping_start_end_points(self):
+ # Test case from https://github.com/googlefonts/fontmake/issues/572.
+ tpen = _TestSegmentPen()
+ ppen = PointToSegmentPen(tpen, outputImpliedClosingLine=False)
+ # The last oncurve point on this closed contour is a "line" segment and has
+ # same coordinates as the starting point.
+ ppen.beginPath()
+ ppen.addPoint((0, 651), segmentType="line")
+ ppen.addPoint((0, 101), segmentType="line")
+ ppen.addPoint((0, 101), segmentType="line")
+ ppen.addPoint((0, 651), segmentType="line")
+ ppen.endPath()
+ # Check that we always output an explicit 'lineTo' segment at the end,
+ # regardless of the value of 'outputImpliedClosingLine', to disambiguate
+ # the duplicate point from the implied closing line.
+ self.assertEqual(
+ "0 651 moveto "
+ "0 101 lineto "
+ "0 101 lineto "
+ "0 651 lineto "
+ "0 651 lineto "
+ "closepath",
+ repr(tpen)
+ )
+
+ def test_roundTrip2(self):
+ tpen = _TestPointPen()
+ ppen = PointToSegmentPen(SegmentToPointPen(tpen))
+ ppen.beginPath()
+ ppen.addPoint((0, 651), segmentType="line")
+ ppen.addPoint((0, 101), segmentType="line")
+ ppen.addPoint((0, 101), segmentType="line")
+ ppen.addPoint((0, 651), segmentType="line")
+ ppen.endPath()
+ self.assertEqual(
+ "beginPath() "
+ "addPoint((0, 651), segmentType='line') "
+ "addPoint((0, 101), segmentType='line') "
+ "addPoint((0, 101), segmentType='line') "
+ "addPoint((0, 651), segmentType='line') "
+ "endPath()",
+ repr(tpen)
+ )
+
class TestSegmentToPointPen(unittest.TestCase):
@@ -198,10 +256,10 @@ class TestSegmentToPointPen(unittest.TestCase):
pen.closePath()
self.assertEqual("beginPath() addPoint((10, 10), segmentType='line') "
"addPoint((10, 20)) addPoint((20, 20)) "
- "addPoint((20, 10), segmentType=qcurve) endPath()",
+ "addPoint((20, 10), segmentType='qcurve') endPath()",
repr(tpen))
- def test_quad(self):
+ def test_quad2(self):
tpen = _TestPointPen()
pen = SegmentToPointPen(tpen)
pen.qCurveTo((10, 20), (20, 20), (20, 10), (10, 10), None)
@@ -410,3 +468,23 @@ class TestReverseContourPointPen(unittest.TestCase):
"endPath() "
"addComponent('base', [1, 0, 0, 1, 0, 0], identifier='foo')",
repr(tpen))
+
+ def test_closed_line_overlapping_start_end_points(self):
+ # Test case from https://github.com/googlefonts/fontmake/issues/572
+ tpen = _TestPointPen()
+ pen = ReverseContourPointPen(tpen)
+ pen.beginPath()
+ pen.addPoint((0, 651), segmentType="line")
+ pen.addPoint((0, 101), segmentType="line")
+ pen.addPoint((0, 101), segmentType="line")
+ pen.addPoint((0, 651), segmentType="line")
+ pen.endPath()
+ self.assertEqual(
+ "beginPath() "
+ "addPoint((0, 651), segmentType='line') "
+ "addPoint((0, 651), segmentType='line') "
+ "addPoint((0, 101), segmentType='line') "
+ "addPoint((0, 101), segmentType='line') "
+ "endPath()",
+ repr(tpen)
+ )
diff --git a/Tests/pens/quartzPen_test.py b/Tests/pens/quartzPen_test.py
new file mode 100644
index 00000000..3a81d97f
--- /dev/null
+++ b/Tests/pens/quartzPen_test.py
@@ -0,0 +1,78 @@
+import unittest
+
+try:
+ from fontTools.pens.quartzPen import QuartzPen
+
+ from Quartz.CoreGraphics import CGPathApply
+ from Quartz.CoreGraphics import kCGPathElementMoveToPoint
+ from Quartz.CoreGraphics import kCGPathElementAddLineToPoint
+ from Quartz.CoreGraphics import kCGPathElementAddQuadCurveToPoint
+ from Quartz.CoreGraphics import kCGPathElementAddCurveToPoint
+ from Quartz.CoreGraphics import kCGPathElementCloseSubpath
+
+ PATH_ELEMENTS = {
+ # CG constant key desc num_points
+ kCGPathElementMoveToPoint: ('moveto', 1),
+ kCGPathElementAddLineToPoint: ('lineto', 1),
+ kCGPathElementAddCurveToPoint: ('curveto', 3),
+ kCGPathElementAddQuadCurveToPoint: ('qcurveto', 2),
+ kCGPathElementCloseSubpath: ('close', 0),
+ }
+
+ PYOBJC_AVAILABLE = True
+except ImportError:
+ PYOBJC_AVAILABLE = False
+
+
+def draw(pen):
+ pen.moveTo((50, 0))
+ pen.lineTo((50, 500))
+ pen.lineTo((200, 500))
+ pen.curveTo((350, 500), (450, 400), (450, 250))
+ pen.curveTo((450, 100), (350, 0), (200, 0))
+ pen.closePath()
+
+
+def quartzPathApplier(elements, element):
+ num_points = 0
+ elem_type = None
+ if element.type in PATH_ELEMENTS:
+ num_points = PATH_ELEMENTS[element.type][1]
+ elem_type = PATH_ELEMENTS[element.type][0]
+ elements.append((elem_type, element.points.as_tuple(num_points)))
+
+
+def quartzPathElements(path):
+ elements = []
+ CGPathApply(path, elements, quartzPathApplier)
+ return elements
+
+
+def quartzPathToString(path):
+ elements = quartzPathElements(path)
+ output = []
+ for element in elements:
+ elem_type, elem_points = element
+ path_points = " ".join([f"{p.x} {p.y}" for p in elem_points])
+ output.append(f"{elem_type} {path_points}")
+ return " ".join(output)
+
+
+@unittest.skipUnless(PYOBJC_AVAILABLE, "pyobjc not installed")
+class QuartzPenTest(unittest.TestCase):
+ def test_draw(self):
+ pen = QuartzPen(None)
+ draw(pen)
+ self.assertEqual(
+ "moveto 50.0 0.0 lineto 50.0 500.0 lineto 200.0 500.0 curveto 350.0 500.0 450.0 400.0 450.0 250.0 curveto 450.0 100.0 350.0 0.0 200.0 0.0 close ",
+ quartzPathToString(pen.path)
+ )
+
+ def test_empty(self):
+ pen = QuartzPen(None)
+ self.assertEqual("", quartzPathToString(pen.path))
+
+
+if __name__ == '__main__':
+ import sys
+ sys.exit(unittest.main())
diff --git a/Tests/pens/recordingPen_test.py b/Tests/pens/recordingPen_test.py
index fdc5d06a..6977b93b 100644
--- a/Tests/pens/recordingPen_test.py
+++ b/Tests/pens/recordingPen_test.py
@@ -1,20 +1,29 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.pens.recordingPen import RecordingPen, DecomposingRecordingPen
+from fontTools.pens.recordingPen import (
+ RecordingPen,
+ DecomposingRecordingPen,
+ RecordingPointPen,
+)
import pytest
class _TestGlyph(object):
-
def draw(self, pen):
pen.moveTo((0.0, 0.0))
pen.lineTo((0.0, 100.0))
pen.curveTo((50.0, 75.0), (60.0, 50.0), (50.0, 0.0))
pen.closePath()
+ def drawPoints(self, pen):
+ pen.beginPath(identifier="abc")
+ pen.addPoint((0.0, 0.0), "line", False, "start", identifier="0000")
+ pen.addPoint((0.0, 100.0), "line", False, None, identifier="0001")
+ pen.addPoint((50.0, 75.0), None, False, None, identifier="0002")
+ pen.addPoint((60.0, 50.0), None, False, None, identifier="0003")
+ pen.addPoint((50.0, 0.0), "curve", True, "last", identifier="0004")
+ pen.endPath()
-class RecordingPenTest(object):
+class RecordingPenTest(object):
def test_addComponent(self):
pen = RecordingPen()
pen.addComponent("a", (2, 0, 0, 3, -10, 5))
@@ -22,18 +31,42 @@ class RecordingPenTest(object):
class DecomposingRecordingPenTest(object):
-
def test_addComponent_decomposed(self):
pen = DecomposingRecordingPen({"a": _TestGlyph()})
pen.addComponent("a", (2, 0, 0, 3, -10, 5))
assert pen.value == [
- ('moveTo', ((-10.0, 5.0),)),
- ('lineTo', ((-10.0, 305.0),)),
- ('curveTo', ((90.0, 230.0), (110.0, 155.0), (90.0, 5.0),)),
- ('closePath', ())]
+ ("moveTo", ((-10.0, 5.0),)),
+ ("lineTo", ((-10.0, 305.0),)),
+ ("curveTo", ((90.0, 230.0), (110.0, 155.0), (90.0, 5.0))),
+ ("closePath", ()),
+ ]
def test_addComponent_missing_raises(self):
pen = DecomposingRecordingPen(dict())
with pytest.raises(KeyError) as excinfo:
pen.addComponent("a", (1, 0, 0, 1, 0, 0))
assert excinfo.value.args[0] == "a"
+
+
+class RecordingPointPenTest:
+ def test_record_and_replay(self):
+ pen = RecordingPointPen()
+ glyph = _TestGlyph()
+ glyph.drawPoints(pen)
+ pen.addComponent("a", (2, 0, 0, 2, -10, 5))
+
+ assert pen.value == [
+ ("beginPath", (), {"identifier": "abc"}),
+ ("addPoint", ((0.0, 0.0), "line", False, "start"), {"identifier": "0000"}),
+ ("addPoint", ((0.0, 100.0), "line", False, None), {"identifier": "0001"}),
+ ("addPoint", ((50.0, 75.0), None, False, None), {"identifier": "0002"}),
+ ("addPoint", ((60.0, 50.0), None, False, None), {"identifier": "0003"}),
+ ("addPoint", ((50.0, 0.0), "curve", True, "last"), {"identifier": "0004"}),
+ ("endPath", (), {}),
+ ("addComponent", ("a", (2, 0, 0, 2, -10, 5)), {}),
+ ]
+
+ pen2 = RecordingPointPen()
+ pen.replay(pen2)
+
+ assert pen2.value == pen.value
diff --git a/Tests/pens/reverseContourPen_test.py b/Tests/pens/reverseContourPen_test.py
index 6dbc5e00..9c715404 100644
--- a/Tests/pens/reverseContourPen_test.py
+++ b/Tests/pens/reverseContourPen_test.py
@@ -278,6 +278,26 @@ TEST_DATA = [
('lineTo', ((848, 348),)), # the duplicate point is kept
('closePath', ())
]
+ ),
+ # Test case from https://github.com/googlefonts/fontmake/issues/572
+ # An additional closing lineTo is required to disambiguate a duplicate
+ # point at the end of a contour from the implied closing line.
+ (
+ [
+ ('moveTo', ((0, 651),)),
+ ('lineTo', ((0, 101),)),
+ ('lineTo', ((0, 101),)),
+ ('lineTo', ((0, 651),)),
+ ('lineTo', ((0, 651),)),
+ ('closePath', ())
+ ],
+ [
+ ('moveTo', ((0, 651),)),
+ ('lineTo', ((0, 651),)),
+ ('lineTo', ((0, 101),)),
+ ('lineTo', ((0, 101),)),
+ ('closePath', ())
+ ]
)
]
diff --git a/Tests/pens/t2CharStringPen_test.py b/Tests/pens/t2CharStringPen_test.py
index c09d8108..b710df55 100644
--- a/Tests/pens/t2CharStringPen_test.py
+++ b/Tests/pens/t2CharStringPen_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.pens.t2CharStringPen import T2CharStringPen
import unittest
@@ -8,16 +6,12 @@ class T2CharStringPenTest(unittest.TestCase):
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
- # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
- # and fires deprecation warnings if a program uses the old name.
- if not hasattr(self, "assertRaisesRegex"):
- self.assertRaisesRegex = self.assertRaisesRegexp
def assertAlmostEqualProgram(self, expected, actual):
self.assertEqual(len(expected), len(actual))
for i1, i2 in zip(expected, actual):
- if isinstance(i1, basestring):
- self.assertIsInstance(i2, basestring)
+ if isinstance(i1, str):
+ self.assertIsInstance(i2, str)
self.assertEqual(i1, i2)
else:
self.assertAlmostEqual(i1, i2)
diff --git a/Tests/pens/ttGlyphPen_test.py b/Tests/pens/ttGlyphPen_test.py
index ede240ad..53db025c 100644
--- a/Tests/pens/ttGlyphPen_test.py
+++ b/Tests/pens/ttGlyphPen_test.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
import os
import unittest
import struct
@@ -240,6 +237,58 @@ class TTGlyphPenTest(TestCase):
with self.assertRaises(struct.error):
compositeGlyph.compile({'a': baseGlyph})
+ def assertGlyphBoundsEqual(self, glyph, bounds):
+ self.assertEqual((glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax), bounds)
+
+ def test_round_float_coordinates_and_component_offsets(self):
+ glyphSet = {}
+ pen = TTGlyphPen(glyphSet)
+
+ pen.moveTo((0, 0))
+ pen.lineTo((0, 1))
+ pen.lineTo((367.6, 0))
+ pen.closePath()
+ simpleGlyph = pen.glyph()
+
+ simpleGlyph.recalcBounds(glyphSet)
+ self.assertGlyphBoundsEqual(simpleGlyph, (0, 0, 368, 1))
+
+ componentName = 'a'
+ glyphSet[componentName] = simpleGlyph
+
+ pen.addComponent(componentName, (1, 0, 0, 1, -86.4, 0))
+ compositeGlyph = pen.glyph()
+
+ compositeGlyph.recalcBounds(glyphSet)
+ self.assertGlyphBoundsEqual(compositeGlyph, (-86, 0, 282, 1))
+
+ def test_scaled_component_bounds(self):
+ glyphSet = {}
+
+ pen = TTGlyphPen(glyphSet)
+ pen.moveTo((-231, 939))
+ pen.lineTo((-55, 939))
+ pen.lineTo((-55, 745))
+ pen.lineTo((-231, 745))
+ pen.closePath()
+ glyphSet["gravecomb"] = gravecomb = pen.glyph()
+
+ pen = TTGlyphPen(glyphSet)
+ pen.moveTo((-278, 939))
+ pen.lineTo((8, 939))
+ pen.lineTo((8, 745))
+ pen.lineTo((-278, 745))
+ pen.closePath()
+ glyphSet["circumflexcomb"] = circumflexcomb = pen.glyph()
+
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("circumflexcomb", (1, 0, 0, 1, 0, 0))
+ pen.addComponent("gravecomb", (0.9, 0, 0, 0.9, 198, 180))
+ glyphSet["uni0302_uni0300"] = uni0302_uni0300 = pen.glyph()
+
+ uni0302_uni0300.recalcBounds(glyphSet)
+ self.assertGlyphBoundsEqual(uni0302_uni0300, (-278, 745, 148, 1025))
+
class _TestGlyph(object):
def __init__(self, glyph):
diff --git a/Tests/pens/utils.py b/Tests/pens/utils.py
new file mode 100644
index 00000000..dced3c1b
--- /dev/null
+++ b/Tests/pens/utils.py
@@ -0,0 +1,281 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from . import CUBIC_GLYPHS
+from fontTools.pens.pointPen import PointToSegmentPen, SegmentToPointPen
+from math import isclose
+import unittest
+
+
+class BaseDummyPen(object):
+ """Base class for pens that record the commands they are called with."""
+
+ def __init__(self, *args, **kwargs):
+ self.commands = []
+
+ def __str__(self):
+ """Return the pen commands as a string of python code."""
+ return _repr_pen_commands(self.commands)
+
+ def addComponent(self, glyphName, transformation, **kwargs):
+ self.commands.append(('addComponent', (glyphName, transformation), kwargs))
+
+
+class DummyPen(BaseDummyPen):
+ """A SegmentPen that records the commands it's called with."""
+
+ def moveTo(self, pt):
+ self.commands.append(('moveTo', (pt,), {}))
+
+ def lineTo(self, pt):
+ self.commands.append(('lineTo', (pt,), {}))
+
+ def curveTo(self, *points):
+ self.commands.append(('curveTo', points, {}))
+
+ def qCurveTo(self, *points):
+ self.commands.append(('qCurveTo', points, {}))
+
+ def closePath(self):
+ self.commands.append(('closePath', tuple(), {}))
+
+ def endPath(self):
+ self.commands.append(('endPath', tuple(), {}))
+
+
+class DummyPointPen(BaseDummyPen):
+ """A PointPen that records the commands it's called with."""
+
+ def beginPath(self, **kwargs):
+ self.commands.append(('beginPath', tuple(), kwargs))
+
+ def endPath(self):
+ self.commands.append(('endPath', tuple(), {}))
+
+ def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
+ kwargs['segmentType'] = str(segmentType) if segmentType else None
+ kwargs['smooth'] = smooth
+ kwargs['name'] = name
+ self.commands.append(('addPoint', (pt,), kwargs))
+
+
+class DummyGlyph(object):
+ """Provides a minimal interface for storing a glyph's outline data in a
+ SegmentPen-oriented way. The glyph's outline consists in the list of
+ SegmentPen commands required to draw it.
+ """
+
+ # the SegmentPen class used to draw on this glyph type
+ DrawingPen = DummyPen
+
+ def __init__(self, glyph=None):
+ """If another glyph (i.e. any object having a 'draw' method) is given,
+ its outline data is copied to self.
+ """
+ self._pen = self.DrawingPen()
+ self.outline = self._pen.commands
+ if glyph:
+ self.appendGlyph(glyph)
+
+ def appendGlyph(self, glyph):
+ """Copy another glyph's outline onto self."""
+ glyph.draw(self._pen)
+
+ def getPen(self):
+ """Return the SegmentPen that can 'draw' on this glyph."""
+ return self._pen
+
+ def getPointPen(self):
+ """Return a PointPen adapter that can 'draw' on this glyph."""
+ return PointToSegmentPen(self._pen)
+
+ def draw(self, pen):
+ """Use another SegmentPen to replay the glyph's outline commands."""
+ if self.outline:
+ for cmd, args, kwargs in self.outline:
+ getattr(pen, cmd)(*args, **kwargs)
+
+ def drawPoints(self, pointPen):
+ """Use another PointPen to replay the glyph's outline commands,
+ indirectly through an adapter.
+ """
+ pen = SegmentToPointPen(pointPen)
+ self.draw(pen)
+
+ def __eq__(self, other):
+ """Return True if 'other' glyph's outline is the same as self."""
+ if hasattr(other, 'outline'):
+ return self.outline == other.outline
+ elif hasattr(other, 'draw'):
+ return self.outline == self.__class__(other).outline
+ return NotImplemented
+
+ def __ne__(self, other):
+ """Return True if 'other' glyph's outline is different from self."""
+ return not (self == other)
+
+ def approx(self, other, rel_tol=1e-12):
+ if hasattr(other, 'outline'):
+ outline2 == other.outline
+ elif hasattr(other, 'draw'):
+ outline2 = self.__class__(other).outline
+ else:
+ raise TypeError(type(other).__name__)
+ outline1 = self.outline
+ if len(outline1) != len(outline2):
+ return False
+ for (cmd1, arg1, kwd1), (cmd2, arg2, kwd2) in zip(outline1, outline2):
+ if cmd1 != cmd2:
+ return False
+ if kwd1 != kwd2:
+ return False
+ if arg1:
+ if isinstance(arg1[0], tuple):
+ if not arg2 or not isinstance(arg2[0], tuple):
+ return False
+ for (x1, y1), (x2, y2) in zip(arg1, arg2):
+ if (
+ not isclose(x1, x2, rel_tol=rel_tol) or
+ not isclose(y1, y2, rel_tol=rel_tol)
+ ):
+ return False
+ elif arg1 != arg2:
+ return False
+ elif arg2:
+ return False
+ return True
+
+ def __str__(self):
+ """Return commands making up the glyph's outline as a string."""
+ return str(self._pen)
+
+
+class DummyPointGlyph(DummyGlyph):
+ """Provides a minimal interface for storing a glyph's outline data in a
+ PointPen-oriented way. The glyph's outline consists in the list of
+ PointPen commands required to draw it.
+ """
+
+ # the PointPen class used to draw on this glyph type
+ DrawingPen = DummyPointPen
+
+ def appendGlyph(self, glyph):
+ """Copy another glyph's outline onto self."""
+ glyph.drawPoints(self._pen)
+
+ def getPen(self):
+ """Return a SegmentPen adapter that can 'draw' on this glyph."""
+ return SegmentToPointPen(self._pen)
+
+ def getPointPen(self):
+ """Return the PointPen that can 'draw' on this glyph."""
+ return self._pen
+
+ def draw(self, pen):
+ """Use another SegmentPen to replay the glyph's outline commands,
+ indirectly through an adapter.
+ """
+ pointPen = PointToSegmentPen(pen)
+ self.drawPoints(pointPen)
+
+ def drawPoints(self, pointPen):
+ """Use another PointPen to replay the glyph's outline commands."""
+ if self.outline:
+ for cmd, args, kwargs in self.outline:
+ getattr(pointPen, cmd)(*args, **kwargs)
+
+
+def _repr_pen_commands(commands):
+ """
+ >>> print(_repr_pen_commands([
+ ... ('moveTo', tuple(), {}),
+ ... ('lineTo', ((1.0, 0.1),), {}),
+ ... ('curveTo', ((1.0, 0.1), (2.0, 0.2), (3.0, 0.3)), {})
+ ... ]))
+ pen.moveTo()
+ pen.lineTo((1, 0.1))
+ pen.curveTo((1, 0.1), (2, 0.2), (3, 0.3))
+
+ >>> print(_repr_pen_commands([
+ ... ('beginPath', tuple(), {}),
+ ... ('addPoint', ((1.0, 0.1),),
+ ... {"segmentType":"line", "smooth":True, "name":"test", "z":1}),
+ ... ]))
+ pen.beginPath()
+ pen.addPoint((1, 0.1), name='test', segmentType='line', smooth=True, z=1)
+
+ >>> print(_repr_pen_commands([
+ ... ('addComponent', ('A', (1, 0, 0, 1, 0, 0)), {})
+ ... ]))
+ pen.addComponent('A', (1, 0, 0, 1, 0, 0))
+ """
+ s = []
+ for cmd, args, kwargs in commands:
+ if args:
+ if isinstance(args[0], tuple):
+ # cast float to int if there're no digits after decimal point,
+ # and round floats to 12 decimal digits (more than enough)
+ args = [
+ tuple((int(v) if int(v) == v else round(v, 12)) for v in pt)
+ for pt in args
+ ]
+ args = ", ".join(repr(a) for a in args)
+ if kwargs:
+ kwargs = ", ".join("%s=%r" % (k, v)
+ for k, v in sorted(kwargs.items()))
+ if args and kwargs:
+ s.append("pen.%s(%s, %s)" % (cmd, args, kwargs))
+ elif args:
+ s.append("pen.%s(%s)" % (cmd, args))
+ elif kwargs:
+ s.append("pen.%s(%s)" % (cmd, kwargs))
+ else:
+ s.append("pen.%s()" % cmd)
+ return "\n".join(s)
+
+
+class TestDummyGlyph(unittest.TestCase):
+
+ def test_equal(self):
+ # verify that the copy and the copy of the copy are equal to
+ # the source glyph's outline, as well as to each other
+ source = CUBIC_GLYPHS['a']
+ copy = DummyGlyph(source)
+ copy2 = DummyGlyph(copy)
+ self.assertEqual(source, copy)
+ self.assertEqual(source, copy2)
+ self.assertEqual(copy, copy2)
+ # assert equality doesn't hold any more after modification
+ copy.outline.pop()
+ self.assertNotEqual(source, copy)
+ self.assertNotEqual(copy, copy2)
+
+
+class TestDummyPointGlyph(unittest.TestCase):
+
+ def test_equal(self):
+ # same as above but using the PointPen protocol
+ source = CUBIC_GLYPHS['a']
+ copy = DummyPointGlyph(source)
+ copy2 = DummyPointGlyph(copy)
+ self.assertEqual(source, copy)
+ self.assertEqual(source, copy2)
+ self.assertEqual(copy, copy2)
+ copy.outline.pop()
+ self.assertNotEqual(source, copy)
+ self.assertNotEqual(copy, copy2)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Tests/subset/data/CmapSubsetTest.subset.ttx b/Tests/subset/data/CmapSubsetTest.subset.ttx
new file mode 100644
index 00000000..10b94a34
--- /dev/null
+++ b/Tests/subset/data/CmapSubsetTest.subset.ttx
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.18">
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ </cmap_format_4>
+ </cmap>
+
+</ttFont>
diff --git a/Tests/subset/data/CmapSubsetTest.ttx b/Tests/subset/data/CmapSubsetTest.ttx
new file mode 100644
index 00000000..ffbfae7f
--- /dev/null
+++ b/Tests/subset/data/CmapSubsetTest.ttx
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.18">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="a"/>
+ <GlyphID id="2" name="basket"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="0.0"/>
+ <checkSumAdjustment value="0xc643119c"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Tue Jan 12 16:39:39 2021"/>
+ <modified value="Tue Jan 12 16:39:39 2021"/>
+ <xMin value="50"/>
+ <yMin value="-200"/>
+ <xMax value="450"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="942"/>
+ <minLeftSideBearing value="50"/>
+ <minRightSideBearing value="50"/>
+ <xMaxExtent value="450"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="3"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="3"/>
+ <maxPoints value="8"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="660"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000100"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000010 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="97"/>
+ <usLastCharIndex value="65535"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="a" width="538" lsb="0"/>
+ <mtx name="basket" width="942" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ </cmap_format_4>
+ <cmap_format_12 platformID="0" platEncID="4" format="12" reserved="0" length="40" language="0" nGroups="2">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ <map code="0x1f9fa" name="basket"/><!-- BASKET -->
+ </cmap_format_12>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ </cmap_format_4>
+ <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="40" language="0" nGroups="2">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ <map code="0x1f9fa" name="basket"/><!-- BASKET -->
+ </cmap_format_12>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="a"/><!-- contains no outline data -->
+
+ <TTGlyph name="basket"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ New Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 0.000;NONE;NewFont-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ New Font Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 0.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ NewFont-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="basket"/>
+ </extraNames>
+ </post>
+
+</ttFont>
diff --git a/Tests/subset/data/GPOS_PairPos_Format2_ClassDef1_useClass0.subset.ttx b/Tests/subset/data/GPOS_PairPos_Format2_ClassDef1_useClass0.subset.ttx
new file mode 100644
index 00000000..3df9aa8f
--- /dev/null
+++ b/Tests/subset/data/GPOS_PairPos_Format2_ClassDef1_useClass0.subset.ttx
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.21">
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="latn"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="2">
+ <Coverage>
+ <Glyph value="g33"/>
+ </Coverage>
+ <ValueFormat1 value="1"/>
+ <ValueFormat2 value="0"/>
+ <ClassDef1>
+ </ClassDef1>
+ <ClassDef2>
+ <ClassDef glyph="g33" class="1"/>
+ </ClassDef2>
+ <!-- Class1Count=1 -->
+ <!-- Class2Count=2 -->
+ <Class1Record index="0">
+ <Class2Record index="0">
+ <Value1 XPlacement="0"/>
+ </Class2Record>
+ <Class2Record index="1">
+ <Value1 XPlacement="-100"/>
+ </Class2Record>
+ </Class1Record>
+ </PairPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/subset/data/GPOS_PairPos_Format2_ClassDef2_useClass0.subset.ttx b/Tests/subset/data/GPOS_PairPos_Format2_ClassDef2_useClass0.subset.ttx
new file mode 100644
index 00000000..dc599f13
--- /dev/null
+++ b/Tests/subset/data/GPOS_PairPos_Format2_ClassDef2_useClass0.subset.ttx
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.21">
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=0 -->
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=0 -->
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=0 -->
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/subset/data/GPOS_PairPos_Format2_PR_2221.ttx b/Tests/subset/data/GPOS_PairPos_Format2_PR_2221.ttx
new file mode 100644
index 00000000..d5132d15
--- /dev/null
+++ b/Tests/subset/data/GPOS_PairPos_Format2_PR_2221.ttx
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.21">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="g33"/>
+ <GlyphID id="2" name="g35"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x3d6ba467"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000001"/>
+ <unitsPerEm value="1500"/>
+ <created value="Thu Jan 1 00:00:00 1970"/>
+ <modified value="Mon Mar 29 14:18:07 2021"/>
+ <xMin value="24"/>
+ <yMin value="-31"/>
+ <xMax value="1000"/>
+ <yMax value="689"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="2500"/>
+ <descent value="0"/>
+ <lineGap value="200"/>
+ <advanceWidthMax value="1500"/>
+ <minLeftSideBearing value="300"/>
+ <minRightSideBearing value="224"/>
+ <xMaxExtent value="1276"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="3"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="2"/>
+ <xAvgCharWidth value="2500"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001100"/>
+ <ySubscriptXSize value="500"/>
+ <ySubscriptYSize value="500"/>
+ <ySubscriptXOffset value="250"/>
+ <ySubscriptYOffset value="50"/>
+ <ySuperscriptXSize value="500"/>
+ <ySuperscriptYSize value="500"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="500"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="500"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="10"/>
+ <bWeight value="6"/>
+ <bProportion value="3"/>
+ <bContrast value="6"/>
+ <bStrokeVariation value="5"/>
+ <bArmStyle value="11"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="ADBE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="33"/>
+ <usLastCharIndex value="35"/>
+ <sTypoAscender value="2500"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="2500"/>
+ <usWinDescent value="0"/>
+ <ulCodePageRange1 value="11100000 00111111 00000001 11111111"/>
+ <ulCodePageRange2 value="11111111 11111111 00000000 00000000"/>
+ <sxHeight value="2500"/>
+ <sCapHeight value="2500"/>
+ <usDefaultChar value="65"/>
+ <usBreakChar value="65"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ gpos2_2_font5
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ gpos2_2_font5
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ gpos2_2_font5
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version1.0
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ gpos2_2_font5
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x21" name="g33"/><!-- EXCLAMATION MARK -->
+ <map code="0x23" name="g35"/><!-- NUMBER SIGN -->
+ </cmap_format_4>
+ </cmap>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CFF>
+ <major value="1"/>
+ <minor value="0"/>
+ <CFFFont name="dummy">
+ <version value="001.000"/>
+ <Notice value="Copyright (c) 2002 Adobe Systems Incorporated. All Rights Reserved."/>
+ <FullName value="dummy"/>
+ <FamilyName value="dummy"/>
+ <Weight value="Regular"/>
+ <isFixedPitch value="0"/>
+ <ItalicAngle value="0"/>
+ <UnderlinePosition value="-125"/>
+ <UnderlineThickness value="50"/>
+ <PaintType value="0"/>
+ <CharstringType value="2"/>
+ <FontMatrix value="0.0008 0 0 0.0008 0 0"/>
+ <UniqueID value="44788"/>
+ <FontBBox value="24 -31 1000 689"/>
+ <StrokeWidth value="0"/>
+ <!-- charset is dumped separately as the 'GlyphOrder' element -->
+ <Encoding name="StandardEncoding"/>
+ <Private>
+ <BlueValues value="-25 0 657 682 439 464 640 653 708 733 475 500"/>
+ <OtherBlues value="283 308 -251 -226 -154 -129 -194 -169"/>
+ <FamilyBlues value="-25 0 657 682 439 464 640 653 708 733 475 500"/>
+ <FamilyOtherBlues value="283 308 -251 -226 -154 -129 -194 -169"/>
+ <BlueScale value="0.039625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="1"/>
+ <StdHW value="32"/>
+ <StdVW value="85"/>
+ <StemSnapH value="32"/>
+ <StemSnapV value="85 90"/>
+ <ForceBold value="0"/>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
+ <initialRandomSeed value="0"/>
+ <defaultWidthX value="2500"/>
+ <nominalWidthX value="2500"/>
+ <Subrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ <CharString index="0">
+ 92 580 rmoveto
+ 13 6 13 7 14 4 54 16 184 1 9 -81 1 -13 -3 -13 -3 -14 -9 -45 -124 -14 -42 -8 rrcurveto
+ -2 -2 1 -1 hhcurveto
+ -2 vlineto
+ -30 -15 5 -40 35 -4 60 -5 62 -4 47 -43 83 -75 -108 -134 -82 -20 -75 -17 -101 91 -42 -14 -22 -8 -7 -18 10 -21 2 -2 2 -2 1 -2 10 -10 11 -3 10 2 rrcurveto
+ 2 2 -1 1 hhcurveto
+ 16 -7 15 -7 15 -7 33 -14 33 -14 35 -7 103 -18 81 94 48 78 51 83 -64 98 -77 36 -4 1 -3 2 -4 2 17 7 16 9 15 12 77 61 -32 107 -79 40 -91 47 -115 -9 -91 -40 rrcurveto
+ -27 -24 18 -37 36 7 rrcurveto
+ 408 -580 rmoveto
+ return
+ </CharString>
+ <CharString index="1">
+ 41 642 rmoveto
+ 1 -2 1 -1 -1 vvcurveto
+ -7 2 -7 5 -5 vhcurveto
+ 15 -69 -71 -105 61 -45 71 -50 214 60 48 -116 9 -20 3 -24 -3 -22 -13 -128 -51 -35 -120 -6 -38 -1 -62 -5 -26 34 -29 22 -33 -28 16 -33 39 -51 75 0 59 2 83 5 76 21 49 69 rrcurveto
+ 25 36 0 48 11 42 19 72 -43 43 -42 45 -62 68 -159 -25 -76 26 -20 43 44 56 -6 66 101 14 102 -5 103 -1 37 7 0 42 -35 11 -109 1 -110 5 -108 -17 rrcurveto
+ -1 1 0 0 1 vvcurveto
+ -25 33 -45 -26 18 -38 rrcurveto
+ 407 -673 rmoveto
+ return
+ </CharString>
+ </Subrs>
+ </Private>
+ <CharStrings>
+ <CharString name=".notdef">
+ endchar
+ </CharString>
+ <CharString name="g33">
+ -107 callsubr
+ -107 callsubr
+ endchar
+ </CharString>
+ <CharString name="g35">
+ -107 callsubr
+ -106 callsubr
+ endchar
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="latn"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="2">
+ <Coverage Format="1">
+ <Glyph value="g33"/>
+ <Glyph value="g35"/>
+ </Coverage>
+ <ValueFormat1 value="1"/>
+ <ValueFormat2 value="0"/>
+ <ClassDef1 Format="1">
+ <ClassDef glyph="g33" class="1"/>
+ <ClassDef glyph="g35" class="2"/>
+ </ClassDef1>
+ <ClassDef2 Format="1">
+ <ClassDef glyph="g33" class="1"/>
+ </ClassDef2>
+ <!-- Class1Count=3 -->
+ <!-- Class2Count=2 -->
+ <Class1Record index="0">
+ <Class2Record index="0">
+ <Value1 XPlacement="0"/>
+ </Class2Record>
+ <Class2Record index="1">
+ <Value1 XPlacement="0"/>
+ </Class2Record>
+ </Class1Record>
+ <Class1Record index="1">
+ <Class2Record index="0">
+ <Value1 XPlacement="0"/>
+ </Class2Record>
+ <Class2Record index="1">
+ <Value1 XPlacement="-100"/>
+ </Class2Record>
+ </Class1Record>
+ <Class1Record index="2">
+ <Class2Record index="0">
+ <Value1 XPlacement="0"/>
+ </Class2Record>
+ <Class2Record index="1">
+ <Value1 XPlacement="-100"/>
+ </Class2Record>
+ </Class1Record>
+ </PairPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+ <hmtx>
+ <mtx name=".notdef" width="1500" lsb="300"/>
+ <mtx name="g33" width="1500" lsb="300"/>
+ <mtx name="g35" width="1500" lsb="300"/>
+ </hmtx>
+
+</ttFont>
diff --git a/Tests/subset/data/Lobster.subset.ttx b/Tests/subset/data/Lobster.subset.ttx
index c35e570d..8089b246 100644
--- a/Tests/subset/data/Lobster.subset.ttx
+++ b/Tests/subset/data/Lobster.subset.ttx
@@ -490,7 +490,7 @@ This license is available with a FAQ at: http://scripts.sil.org/OFL
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="2">
+ <Coverage>
<Glyph value="one"/>
<Glyph value="three"/>
<Glyph value="two"/>
@@ -616,7 +616,7 @@ This license is available with a FAQ at: http://scripts.sil.org/OFL
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="A" out="A.salt"/>
<Substitution in="B" out="B.salt"/>
</SingleSubst>
@@ -625,7 +625,7 @@ This license is available with a FAQ at: http://scripts.sil.org/OFL
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="I">
<Ligature components="J" glyph="IJ"/>
</LigatureSet>
@@ -635,7 +635,7 @@ This license is available with a FAQ at: http://scripts.sil.org/OFL
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="A" out="A.salt"/>
<Substitution in="B" out="B.salt"/>
</SingleSubst>
diff --git a/Tests/subset/data/TestContextSubstFormat3.ttx b/Tests/subset/data/TestContextSubstFormat3.ttx
new file mode 100644
index 00000000..0ed43ee2
--- /dev/null
+++ b/Tests/subset/data/TestContextSubstFormat3.ttx
@@ -0,0 +1,604 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.9">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="plus"/>
+ <GlyphID id="2" name="glyph00002"/>
+ <GlyphID id="3" name="glyph00003"/>
+ <GlyphID id="4" name="glyph00004"/>
+ <GlyphID id="5" name="glyph00005"/>
+ <GlyphID id="6" name="glyph00006"/>
+ <GlyphID id="7" name="glyph00007"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0xa6bcdc24"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00001111"/>
+ <unitsPerEm value="1000"/>
+ <created value="Mon Nov 21 06:10:39 2016"/>
+ <modified value="Fri Apr 24 05:31:23 2020"/>
+ <xMin value="-1000"/>
+ <yMin value="-509"/>
+ <xMax value="1135"/>
+ <yMax value="1194"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="8"/>
+ <fontDirectionHint value="0"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="977"/>
+ <descent value="-205"/>
+ <lineGap value="67"/>
+ <advanceWidthMax value="1000"/>
+ <minLeftSideBearing value="-1000"/>
+ <minRightSideBearing value="-1000"/>
+ <xMaxExtent value="1135"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="8"/>
+ <maxPoints value="240"/>
+ <maxContours value="41"/>
+ <maxCompositePoints value="163"/>
+ <maxCompositeContours value="12"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="4"/>
+ <maxComponentDepth value="3"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="500"/>
+ <usWeightClass value="500"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="665"/>
+ <ySubscriptYSize value="716"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="143"/>
+ <ySuperscriptXSize value="0"/>
+ <ySuperscriptYSize value="0"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="0"/>
+ <yStrikeoutSize value="51"/>
+ <yStrikeoutPosition value="265"/>
+ <sFamilyClass value="2057"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="6"/>
+ <bProportion value="9"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="BE5N"/>
+ <fsSelection value="00000000 11000000"/>
+ <usFirstCharIndex value="43"/>
+ <usLastCharIndex value="43"/>
+ <sTypoAscender value="977"/>
+ <sTypoDescender value="-272"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="977"/>
+ <usWinDescent value="272"/>
+ <ulCodePageRange1 value="00100000 00000000 00000001 00011111"/>
+ <ulCodePageRange2 value="11000100 00000000 00000000 00000000"/>
+ <sxHeight value="530"/>
+ <sCapHeight value="735"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="8"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="57"/>
+ <mtx name="glyph00002" width="500" lsb="57"/>
+ <mtx name="glyph00003" width="500" lsb="57"/>
+ <mtx name="glyph00004" width="500" lsb="-8"/>
+ <mtx name="glyph00005" width="500" lsb="-8"/>
+ <mtx name="glyph00006" width="500" lsb="-8"/>
+ <mtx name="glyph00007" width="500" lsb="-65"/>
+ <mtx name="plus" width="500" lsb="57"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x2b" name="plus"/><!-- PLUS SIGN -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x2b" name="plus"/><!-- PLUS SIGN -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ <TTGlyph name="glyph00002" xMin="57" yMin="139" xMax="508" yMax="541">
+ <contour>
+ <pt x="203" y="139" on="1"/>
+ <pt x="203" y="298" on="1"/>
+ <pt x="57" y="298" on="1"/>
+ <pt x="57" y="382" on="1"/>
+ <pt x="203" y="382" on="1"/>
+ <pt x="203" y="541" on="1"/>
+ <pt x="297" y="541" on="1"/>
+ <pt x="297" y="382" on="1"/>
+ <pt x="508" y="382" on="1"/>
+ <pt x="508" y="298" on="1"/>
+ <pt x="297" y="298" on="1"/>
+ <pt x="297" y="139" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="glyph00003" xMin="57" yMin="139" xMax="508" yMax="541">
+ <contour>
+ <pt x="260" y="139" on="1"/>
+ <pt x="260" y="298" on="1"/>
+ <pt x="57" y="298" on="1"/>
+ <pt x="57" y="382" on="1"/>
+ <pt x="260" y="382" on="1"/>
+ <pt x="260" y="541" on="1"/>
+ <pt x="354" y="541" on="1"/>
+ <pt x="354" y="382" on="1"/>
+ <pt x="508" y="382" on="1"/>
+ <pt x="508" y="298" on="1"/>
+ <pt x="354" y="298" on="1"/>
+ <pt x="354" y="139" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="glyph00004" xMin="-8" yMin="139" xMax="508" yMax="541">
+ <contour>
+ <pt x="203" y="139" on="1"/>
+ <pt x="203" y="298" on="1"/>
+ <pt x="-8" y="298" on="1"/>
+ <pt x="-8" y="382" on="1"/>
+ <pt x="203" y="382" on="1"/>
+ <pt x="203" y="541" on="1"/>
+ <pt x="297" y="541" on="1"/>
+ <pt x="297" y="382" on="1"/>
+ <pt x="508" y="382" on="1"/>
+ <pt x="508" y="298" on="1"/>
+ <pt x="297" y="298" on="1"/>
+ <pt x="297" y="139" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="glyph00005" xMin="-8" yMin="139" xMax="443" yMax="541">
+ <contour>
+ <pt x="203" y="139" on="1"/>
+ <pt x="203" y="298" on="1"/>
+ <pt x="-8" y="298" on="1"/>
+ <pt x="-8" y="382" on="1"/>
+ <pt x="203" y="382" on="1"/>
+ <pt x="203" y="541" on="1"/>
+ <pt x="297" y="541" on="1"/>
+ <pt x="297" y="382" on="1"/>
+ <pt x="443" y="382" on="1"/>
+ <pt x="443" y="298" on="1"/>
+ <pt x="297" y="298" on="1"/>
+ <pt x="297" y="139" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="glyph00006" xMin="-8" yMin="139" xMax="443" yMax="541">
+ <contour>
+ <pt x="146" y="139" on="1"/>
+ <pt x="146" y="298" on="1"/>
+ <pt x="-8" y="298" on="1"/>
+ <pt x="-8" y="382" on="1"/>
+ <pt x="146" y="382" on="1"/>
+ <pt x="146" y="541" on="1"/>
+ <pt x="240" y="541" on="1"/>
+ <pt x="240" y="382" on="1"/>
+ <pt x="443" y="382" on="1"/>
+ <pt x="443" y="298" on="1"/>
+ <pt x="240" y="298" on="1"/>
+ <pt x="240" y="139" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="glyph00007" xMin="-65" yMin="139" xMax="443" yMax="541">
+ <contour>
+ <pt x="203" y="139" on="1"/>
+ <pt x="203" y="298" on="1"/>
+ <pt x="-65" y="298" on="1"/>
+ <pt x="-65" y="382" on="1"/>
+ <pt x="203" y="382" on="1"/>
+ <pt x="203" y="541" on="1"/>
+ <pt x="297" y="541" on="1"/>
+ <pt x="297" y="382" on="1"/>
+ <pt x="443" y="382" on="1"/>
+ <pt x="443" y="298" on="1"/>
+ <pt x="297" y="298" on="1"/>
+ <pt x="297" y="139" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="plus" xMin="57" yMin="139" xMax="443" yMax="541">
+ <contour>
+ <pt x="203" y="139" on="1"/>
+ <pt x="203" y="298" on="1"/>
+ <pt x="57" y="298" on="1"/>
+ <pt x="57" y="382" on="1"/>
+ <pt x="203" y="382" on="1"/>
+ <pt x="203" y="541" on="1"/>
+ <pt x="297" y="541" on="1"/>
+ <pt x="297" y="382" on="1"/>
+ <pt x="443" y="382" on="1"/>
+ <pt x="443" y="298" on="1"/>
+ <pt x="297" y="298" on="1"/>
+ <pt x="297" y="139" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (c) 2015-2019 Belleve Invis.
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Iosevka Medium
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ Iosevka Medium Version 3.0.0-rc.8
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Iosevka Medium
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 3.0.0-rc.8; ttfautohint (v1.8.3)
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ Iosevka-Medium
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-50"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="1"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="6380"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="1"/>
+ </post>
+
+ <gasp>
+ <gaspRange rangeMaxPPEM="65535" rangeGaspBehavior="15"/>
+ </gasp>
+
+ <GDEF>
+ <Version value="0x00010000"/>
+ <GlyphClassDef>
+ <ClassDef glyph=".notdef" class="1"/>
+ <ClassDef glyph="glyph00002" class="1"/>
+ <ClassDef glyph="glyph00003" class="1"/>
+ <ClassDef glyph="glyph00004" class="1"/>
+ <ClassDef glyph="glyph00005" class="1"/>
+ <ClassDef glyph="glyph00006" class="1"/>
+ <ClassDef glyph="glyph00007" class="1"/>
+ <ClassDef glyph="plus" class="1"/>
+ </GlyphClassDef>
+ </GDEF>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=0 -->
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=0 -->
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=0 -->
+ </LookupList>
+ </GPOS>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=4 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ <ScriptRecord index="1">
+ <ScriptTag value="cyrl"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ <ScriptRecord index="2">
+ <ScriptTag value="grek"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ <ScriptRecord index="3">
+ <ScriptTag value="latn"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="calt"/>
+ <Feature>
+ <!-- LookupCount=2 -->
+ <LookupListIndex index="0" value="0"/>
+ <LookupListIndex index="1" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=6 -->
+ <Lookup index="0">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="2">
+ <Coverage>
+ <Glyph value="plus"/>
+ </Coverage>
+ <BacktrackClassDef>
+ <ClassDef glyph="glyph00005" class="1"/>
+ <ClassDef glyph="glyph00007" class="1"/>
+ </BacktrackClassDef>
+ <InputClassDef>
+ <ClassDef glyph="plus" class="1"/>
+ </InputClassDef>
+ <LookAheadClassDef>
+ </LookAheadClassDef>
+ <!-- ChainSubClassSetCount=2 -->
+ <ChainSubClassSet index="0" empty="1"/>
+ <ChainSubClassSet index="1">
+ <!-- ChainSubClassRuleCount=4 -->
+ <ChainSubClassRule index="0">
+ <!-- BacktrackGlyphCount=1 -->
+ <Backtrack index="0" value="1"/>
+ <!-- InputGlyphCount=1 -->
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=1 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="5"/>
+ </SubstLookupRecord>
+ </ChainSubClassRule>
+ <ChainSubClassRule index="1">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=4 -->
+ <Input index="0" value="1"/>
+ <Input index="1" value="1"/>
+ <Input index="2" value="1"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=4 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="4"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="1">
+ <SequenceIndex value="1"/>
+ <LookupListIndex value="3"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="2">
+ <SequenceIndex value="2"/>
+ <LookupListIndex value="3"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="3">
+ <SequenceIndex value="3"/>
+ <LookupListIndex value="2"/>
+ </SubstLookupRecord>
+ </ChainSubClassRule>
+ <ChainSubClassRule index="2">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=3 -->
+ <Input index="0" value="1"/>
+ <Input index="1" value="1"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=3 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="4"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="1">
+ <SequenceIndex value="1"/>
+ <LookupListIndex value="3"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="2">
+ <SequenceIndex value="2"/>
+ <LookupListIndex value="2"/>
+ </SubstLookupRecord>
+ </ChainSubClassRule>
+ <ChainSubClassRule index="3">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=2 -->
+ <Input index="0" value="1"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- SubstCount=2 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="4"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="1">
+ <SequenceIndex value="1"/>
+ <LookupListIndex value="2"/>
+ </SubstLookupRecord>
+ </ChainSubClassRule>
+ </ChainSubClassSet>
+ </ChainContextSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="5"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=2 -->
+ <ContextSubst index="0" Format="3">
+ <!-- GlyphCount=3 -->
+ <!-- SubstCount=2 -->
+ <Coverage index="0">
+ <Glyph value="glyph00002"/>
+ </Coverage>
+ <Coverage index="1">
+ <Glyph value="glyph00004"/>
+ </Coverage>
+ <Coverage index="2">
+ <Glyph value="glyph00005"/>
+ </Coverage>
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="5"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="1">
+ <SequenceIndex value="2"/>
+ <LookupListIndex value="5"/>
+ </SubstLookupRecord>
+ </ContextSubst>
+ <ContextSubst index="1" Format="3">
+ <!-- GlyphCount=2 -->
+ <!-- SubstCount=2 -->
+ <Coverage index="0">
+ <Glyph value="glyph00002"/>
+ </Coverage>
+ <Coverage index="1">
+ <Glyph value="glyph00005"/>
+ </Coverage>
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="5"/>
+ </SubstLookupRecord>
+ <SubstLookupRecord index="1">
+ <SequenceIndex value="1"/>
+ <LookupListIndex value="5"/>
+ </SubstLookupRecord>
+ </ContextSubst>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="plus" out="glyph00005"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="3">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="plus" out="glyph00004"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="4">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="plus" out="glyph00002"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="5">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="glyph00002" out="glyph00003"/>
+ <Substitution in="glyph00005" out="glyph00006"/>
+ <Substitution in="plus" out="glyph00007"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/subset/data/expect_keep_math.ttx b/Tests/subset/data/expect_keep_math.ttx
index c734dd9c..f2bc41df 100644
--- a/Tests/subset/data/expect_keep_math.ttx
+++ b/Tests/subset/data/expect_keep_math.ttx
@@ -417,7 +417,7 @@
</MathConstants>
<MathGlyphInfo>
<MathItalicsCorrectionInfo>
- <Coverage Format="1">
+ <Coverage>
<Glyph value="u1D435"/>
</Coverage>
<!-- ItalicsCorrectionCount=1 -->
@@ -426,7 +426,7 @@
</ItalicsCorrection>
</MathItalicsCorrectionInfo>
<MathTopAccentAttachment>
- <TopAccentCoverage Format="1">
+ <TopAccentCoverage>
<Glyph value="A"/>
<Glyph value="uni0302"/>
<Glyph value="u1D400"/>
@@ -466,14 +466,14 @@
<Value value="1164"/>
</TopAccentAttachment>
</MathTopAccentAttachment>
- <ExtendedShapeCoverage Format="1">
+ <ExtendedShapeCoverage>
<Glyph value="parenleft.size1"/>
<Glyph value="parenleft.size2"/>
<Glyph value="parenleft.size3"/>
<Glyph value="parenleft.size4"/>
</ExtendedShapeCoverage>
<MathKernInfo>
- <MathKernCoverage Format="1">
+ <MathKernCoverage>
<Glyph value="A"/>
<Glyph value="u1D400"/>
</MathKernCoverage>
@@ -522,10 +522,10 @@
</MathGlyphInfo>
<MathVariants>
<MinConnectorOverlap value="50"/>
- <VertGlyphCoverage Format="1">
+ <VertGlyphCoverage>
<Glyph value="parenleft"/>
</VertGlyphCoverage>
- <HorizGlyphCoverage Format="1">
+ <HorizGlyphCoverage>
<Glyph value="uni0302"/>
</HorizGlyphCoverage>
<!-- VertGlyphCount=1 -->
diff --git a/Tests/subset/data/expect_layout_scripts.ttx b/Tests/subset/data/expect_layout_scripts.ttx
new file mode 100644
index 00000000..bd350832
--- /dev/null
+++ b/Tests/subset/data/expect_layout_scripts.ttx
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=0 -->
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=0 -->
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=0 -->
+ </LookupList>
+ </GPOS>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=2 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="arab"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="2"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=1 -->
+ <LangSysRecord index="0">
+ <LangSysTag value="URD "/>
+ <LangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="1"/>
+ <FeatureIndex index="1" value="2"/>
+ </LangSys>
+ </LangSysRecord>
+ </Script>
+ </ScriptRecord>
+ <ScriptRecord index="1">
+ <ScriptTag value="latn"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="2"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=1 -->
+ <LangSysRecord index="0">
+ <LangSysTag value="TRK "/>
+ <LangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="0"/>
+ <FeatureIndex index="1" value="2"/>
+ </LangSys>
+ </LangSysRecord>
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=3 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="locl"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="2"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="locl"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="2">
+ <FeatureTag value="numr"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=3 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni06F4" out="uni06F4.urd"/>
+ <Substitution in="uni06F6" out="uni06F6.urd"/>
+ <Substitution in="uni06F7" out="uni06F7.urd"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni06F0" out="uni06F0.numr"/>
+ <Substitution in="uni06F1" out="uni06F1.numr"/>
+ <Substitution in="uni06F2" out="uni06F2.numr"/>
+ <Substitution in="uni06F3" out="uni06F3.numr"/>
+ <Substitution in="uni06F4" out="uni06F4.numr"/>
+ <Substitution in="uni06F4.urd" out="uni06F4.urd.numr"/>
+ <Substitution in="uni06F5" out="uni06F5.numr"/>
+ <Substitution in="uni06F6" out="uni06F6.numr"/>
+ <Substitution in="uni06F6.urd" out="uni06F6.urd.numr"/>
+ <Substitution in="uni06F7" out="uni06F7.numr"/>
+ <Substitution in="uni06F7.urd" out="uni06F7.urd.numr"/>
+ <Substitution in="uni06F8" out="uni06F8.numr"/>
+ <Substitution in="uni06F9" out="uni06F9.numr"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="i" out="i.TRK"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/subset/data/layout_scripts.ttx b/Tests/subset/data/layout_scripts.ttx
new file mode 100644
index 00000000..ddf0cd63
--- /dev/null
+++ b/Tests/subset/data/layout_scripts.ttx
@@ -0,0 +1,997 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.17">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="i"/>
+ <GlyphID id="2" name="uni06F0"/>
+ <GlyphID id="3" name="uni06F1"/>
+ <GlyphID id="4" name="uni06F2"/>
+ <GlyphID id="5" name="uni06F3"/>
+ <GlyphID id="6" name="uni06F4"/>
+ <GlyphID id="7" name="uni06F5"/>
+ <GlyphID id="8" name="uni06F6"/>
+ <GlyphID id="9" name="uni06F7"/>
+ <GlyphID id="10" name="uni06F8"/>
+ <GlyphID id="11" name="uni06F9"/>
+ <GlyphID id="12" name="uni06F4.urd"/>
+ <GlyphID id="13" name="uni06F6.urd"/>
+ <GlyphID id="14" name="uni06F7.urd"/>
+ <GlyphID id="15" name="uni06F0.numr"/>
+ <GlyphID id="16" name="uni06F1.numr"/>
+ <GlyphID id="17" name="uni06F2.numr"/>
+ <GlyphID id="18" name="uni06F3.numr"/>
+ <GlyphID id="19" name="uni06F4.numr"/>
+ <GlyphID id="20" name="uni06F5.numr"/>
+ <GlyphID id="21" name="uni06F6.numr"/>
+ <GlyphID id="22" name="uni06F7.numr"/>
+ <GlyphID id="23" name="uni06F8.numr"/>
+ <GlyphID id="24" name="uni06F9.numr"/>
+ <GlyphID id="25" name="uni06F4.urd.numr"/>
+ <GlyphID id="26" name="uni06F6.urd.numr"/>
+ <GlyphID id="27" name="uni06F7.urd.numr"/>
+ <GlyphID id="28" name="i.TRK"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="0.114"/>
+ <checkSumAdjustment value="0xe87b6e18"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri May 7 21:08:01 2010"/>
+ <modified value="Thu Jan 1 00:00:00 1970"/>
+ <xMin value="-581"/>
+ <yMin value="-898"/>
+ <xMax value="11462"/>
+ <yMax value="1814"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1124"/>
+ <descent value="-634"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="11435"/>
+ <minLeftSideBearing value="-581"/>
+ <minRightSideBearing value="-970"/>
+ <xMaxExtent value="11462"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="29"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="29"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="456"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="260"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00100000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="ALIF"/>
+ <fsSelection value="00000000 11000000"/>
+ <usFirstCharIndex value="105"/>
+ <usLastCharIndex value="1785"/>
+ <sTypoAscender value="1124"/>
+ <sTypoDescender value="-634"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="1814"/>
+ <usWinDescent value="897"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 11010011"/>
+ <ulCodePageRange2 value="00000000 00001000 00000000 00000000"/>
+ <sxHeight value="433"/>
+ <sCapHeight value="646"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="8"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright 2010-2020 The Amiri Project Authors (https://github.com/alif-type/amiri).
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Amiri
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 0.114;ALIF;Amiri-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Amiri Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 0.114
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ Amiri-Regular
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x69" name="i"/><!-- LATIN SMALL LETTER I -->
+ <map code="0x6f0" name="uni06F0"/><!-- EXTENDED ARABIC-INDIC DIGIT ZERO -->
+ <map code="0x6f1" name="uni06F1"/><!-- EXTENDED ARABIC-INDIC DIGIT ONE -->
+ <map code="0x6f2" name="uni06F2"/><!-- EXTENDED ARABIC-INDIC DIGIT TWO -->
+ <map code="0x6f3" name="uni06F3"/><!-- EXTENDED ARABIC-INDIC DIGIT THREE -->
+ <map code="0x6f4" name="uni06F4"/><!-- EXTENDED ARABIC-INDIC DIGIT FOUR -->
+ <map code="0x6f5" name="uni06F5"/><!-- EXTENDED ARABIC-INDIC DIGIT FIVE -->
+ <map code="0x6f6" name="uni06F6"/><!-- EXTENDED ARABIC-INDIC DIGIT SIX -->
+ <map code="0x6f7" name="uni06F7"/><!-- EXTENDED ARABIC-INDIC DIGIT SEVEN -->
+ <map code="0x6f8" name="uni06F8"/><!-- EXTENDED ARABIC-INDIC DIGIT EIGHT -->
+ <map code="0x6f9" name="uni06F9"/><!-- EXTENDED ARABIC-INDIC DIGIT NINE -->
+ </cmap_format_4>
+ <cmap_format_12 platformID="0" platEncID="4" format="12" reserved="0" length="40" language="0" nGroups="2">
+ <map code="0x69" name="i"/><!-- LATIN SMALL LETTER I -->
+ <map code="0x6f0" name="uni06F0"/><!-- EXTENDED ARABIC-INDIC DIGIT ZERO -->
+ <map code="0x6f1" name="uni06F1"/><!-- EXTENDED ARABIC-INDIC DIGIT ONE -->
+ <map code="0x6f2" name="uni06F2"/><!-- EXTENDED ARABIC-INDIC DIGIT TWO -->
+ <map code="0x6f3" name="uni06F3"/><!-- EXTENDED ARABIC-INDIC DIGIT THREE -->
+ <map code="0x6f4" name="uni06F4"/><!-- EXTENDED ARABIC-INDIC DIGIT FOUR -->
+ <map code="0x6f5" name="uni06F5"/><!-- EXTENDED ARABIC-INDIC DIGIT FIVE -->
+ <map code="0x6f6" name="uni06F6"/><!-- EXTENDED ARABIC-INDIC DIGIT SIX -->
+ <map code="0x6f7" name="uni06F7"/><!-- EXTENDED ARABIC-INDIC DIGIT SEVEN -->
+ <map code="0x6f8" name="uni06F8"/><!-- EXTENDED ARABIC-INDIC DIGIT EIGHT -->
+ <map code="0x6f9" name="uni06F9"/><!-- EXTENDED ARABIC-INDIC DIGIT NINE -->
+ </cmap_format_12>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x69" name="i"/><!-- LATIN SMALL LETTER I -->
+ <map code="0x6f0" name="uni06F0"/><!-- EXTENDED ARABIC-INDIC DIGIT ZERO -->
+ <map code="0x6f1" name="uni06F1"/><!-- EXTENDED ARABIC-INDIC DIGIT ONE -->
+ <map code="0x6f2" name="uni06F2"/><!-- EXTENDED ARABIC-INDIC DIGIT TWO -->
+ <map code="0x6f3" name="uni06F3"/><!-- EXTENDED ARABIC-INDIC DIGIT THREE -->
+ <map code="0x6f4" name="uni06F4"/><!-- EXTENDED ARABIC-INDIC DIGIT FOUR -->
+ <map code="0x6f5" name="uni06F5"/><!-- EXTENDED ARABIC-INDIC DIGIT FIVE -->
+ <map code="0x6f6" name="uni06F6"/><!-- EXTENDED ARABIC-INDIC DIGIT SIX -->
+ <map code="0x6f7" name="uni06F7"/><!-- EXTENDED ARABIC-INDIC DIGIT SEVEN -->
+ <map code="0x6f8" name="uni06F8"/><!-- EXTENDED ARABIC-INDIC DIGIT EIGHT -->
+ <map code="0x6f9" name="uni06F9"/><!-- EXTENDED ARABIC-INDIC DIGIT NINE -->
+ </cmap_format_4>
+ <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="40" language="0" nGroups="2">
+ <map code="0x69" name="i"/><!-- LATIN SMALL LETTER I -->
+ <map code="0x6f0" name="uni06F0"/><!-- EXTENDED ARABIC-INDIC DIGIT ZERO -->
+ <map code="0x6f1" name="uni06F1"/><!-- EXTENDED ARABIC-INDIC DIGIT ONE -->
+ <map code="0x6f2" name="uni06F2"/><!-- EXTENDED ARABIC-INDIC DIGIT TWO -->
+ <map code="0x6f3" name="uni06F3"/><!-- EXTENDED ARABIC-INDIC DIGIT THREE -->
+ <map code="0x6f4" name="uni06F4"/><!-- EXTENDED ARABIC-INDIC DIGIT FOUR -->
+ <map code="0x6f5" name="uni06F5"/><!-- EXTENDED ARABIC-INDIC DIGIT FIVE -->
+ <map code="0x6f6" name="uni06F6"/><!-- EXTENDED ARABIC-INDIC DIGIT SIX -->
+ <map code="0x6f7" name="uni06F7"/><!-- EXTENDED ARABIC-INDIC DIGIT SEVEN -->
+ <map code="0x6f8" name="uni06F8"/><!-- EXTENDED ARABIC-INDIC DIGIT EIGHT -->
+ <map code="0x6f9" name="uni06F9"/><!-- EXTENDED ARABIC-INDIC DIGIT NINE -->
+ </cmap_format_12>
+ </cmap>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-512"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CFF>
+ <major value="1"/>
+ <minor value="0"/>
+ <CFFFont name="Amiri-Regular">
+ <version value="0.114"/>
+ <Copyright value="Copyright 2010-2020 The Amiri Project Authors https:github.comalif-typeamiri."/>
+ <FullName value="Amiri"/>
+ <FamilyName value="Amiri"/>
+ <Weight value="Regular"/>
+ <isFixedPitch value="0"/>
+ <ItalicAngle value="0"/>
+ <UnderlinePosition value="-512"/>
+ <UnderlineThickness value="50"/>
+ <PaintType value="0"/>
+ <CharstringType value="2"/>
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FontBBox value="-581 -898 11462 1814"/>
+ <StrokeWidth value="0"/>
+ <!-- charset is dumped separately as the 'GlyphOrder' element -->
+ <Encoding name="StandardEncoding"/>
+ <Private>
+ <BlueScale value="0.039625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="1"/>
+ <ForceBold value="0"/>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
+ <initialRandomSeed value="0"/>
+ <defaultWidthX value="0"/>
+ <nominalWidthX value="253"/>
+ <Subrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ <CharString index="0">
+ 3 2 3 1 vhcurveto
+ endchar
+ </CharString>
+ <CharString index="1">
+ 177 113 -106 callgsubr
+ return
+ </CharString>
+ <CharString index="2">
+ 487 531 rmoveto
+ -5 -82 -21 -36 -38 10 -36 10 -16 18 5 25 5 25 1 18 -2 8 -2 8 -5 5 -8 1 -8 1 -4 -7 -3 -13 -14 -69 -26 -36 -40 -4 -29 -3 -23 4 -19 11 rrcurveto
+ -8 5 -24 31 -39 57 -17 26 -12 0 -6 -23 -25 -101 rcurveline
+ -3 -13 5 -16 14 -20 79 -117 45 -148 11 -178 1 -8 2 -6 4 -3 4 -3 4 0 6 2 6 2 3 4 2 7 20 79 -11 123 -42 169 39 -3 35 16 32 35 rrcurveto
+ 14 -11 17 -9 20 -6 63 -20 40 23 18 65 13 49 7 46 2 41 rrcurveto
+ 20 1 -6 9 -10 hhcurveto
+ -10 -5 -6 -12 -1 hvcurveto
+ endchar
+ </CharString>
+ <CharString index="3">
+ rmoveto
+ -45 13 -33 20 -23 28 -3 4 0 3 3 3 27 30 33 15 38 -1 rrcurveto
+ 28 20 -8 -13 13 hvcurveto
+ 5 -5 3 0 4 2 3 1 0 3 -1 6 -6 13 -11 16 -15 16 rrcurveto
+ 14 -13 -14 7 -14 hhcurveto
+ -30 -33 -21 -43 -37 hvcurveto
+ -15 -19 -12 -20 -7 -23 -3 -7 1 -9 3 -8 11 -25 31 -23 53 -19 -51 -45 -38 -53 -24 -60 -5 -12 -1 -6 5 -1 6 -3 7 4 7 10 26 40 32 36 34 30 rrcurveto
+ 37 33 37 24 37 16 10 4 7 8 4 13 16 50 rcurveline
+ 4 10 -5 3 -12 -4 -18 -7 -23 -14 -28 -22 -7 -5 -9 -2 -9 3 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="4">
+ 311 -77 rmoveto
+ 21 89 26 81 29 73 29 73 39 74 45 76 rrcurveto
+ 6 11 2 9 8 vvcurveto
+ -6 97 rlineto
+ 9 -1 -2 4 -5 hhcurveto
+ -5 -5 -6 -10 -5 hvcurveto
+ -79 -149 -53 -135 -27 -121 -4 -19 -7 2 -7 21 -55 159 -61 140 -70 124 -11 20 -8 -8 -5 -35 -13 -81 rcurveline
+ -2 -10 1 -8 3 -6 57 -95 46 -93 38 -90 38 -90 20 -68 2 -45 rrcurveto
+ -7 3 -4 4 -3 vhcurveto
+ 4 -3 4 0 5 2 5 2 2 5 2 7 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="5">
+ rmoveto
+ 19 69 -12 87 -41 106 -10 24 -4 14 2 3 rrcurveto
+ 10 10 11 5 12 hhcurveto
+ 31 25 -26 -51 20 hvcurveto
+ 2 -3 1 -2 3 -1 rrcurveto
+ 2 2 1 3 2 hvcurveto
+ 12 34 21 34 28 35 2 2 2 4 1 4 14 51 rcurveline
+ 3 0 2 -3 1 vhcurveto
+ -3 1 -2 -1 -4 -3 -24 -22 -21 -32 -16 -43 -22 49 -28 24 -36 -2 -32 -1 -21 -19 -12 -38 -12 -39 2 -39 13 -38 29 -80 17 -68 4 -55 rrcurveto
+ -6 3 -2 4 5 -107 callsubr
+ </CharString>
+ <CharString index="6">
+ 158 296 -95 callgsubr
+ </CharString>
+ <CharString index="7">
+ 136 657 rmoveto
+ -13 -53 -16 -49 -17 -44 -18 -43 -22 -44 -27 -46 rrcurveto
+ -4 -6 -1 -6 -5 vvcurveto
+ 3 -58 rlineto
+ -6 2 -2 3 3 3 3 6 3 vhcurveto
+ 48 90 31 81 17 72 2 12 4 -1 4 -13 33 -95 37 -84 42 -75 7 -12 4 5 3 21 8 49 rcurveline
+ 1 6 0 4 -2 4 -34 57 -28 56 -23 54 -22 54 -12 41 -2 27 rrcurveto
+ 4 -1 3 -3 2 vhcurveto
+ -2 1 -3 0 -3 -1 -3 -1 -1 -4 -1 -4 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="8">
+ 220 500 rmoveto
+ -67 -86 -32 -66 2 -47 4 -86 75 -35 146 14 5 -82 22 -87 39 -91 15 -35 9 -2 6 33 18 105 rcurveline
+ 3 15 -3 16 -10 18 -32 59 -18 73 -3 86 -3 87 -16 66 -32 45 -39 56 -43 0 -46 -56 rrcurveto
+ 102 -152 rmoveto
+ 6 -19 -1 -13 -6 -6 rrcurveto
+ -2 -3 -3 -2 -5 hhcurveto
+ -50 -3 -36 6 -22 14 -16 10 -5 16 7 21 12 39 24 15 36 -10 30 -9 21 -22 11 -35 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="9">
+ 53 654 -94 callgsubr
+ </CharString>
+ <CharString index="10">
+ 38 655 -88 callgsubr
+ </CharString>
+ <CharString index="11">
+ hhcurveto
+ 54 3 49 -4 45 -12 5 -1 4 0 2 1 54 31 rcurveline
+ 10 6 0 4 -10 3 -61 14 -61 3 -60 -9 57 83 42 104 25 126 1 7 0 5 -2 2 -2 2 -3 -1 -4 -4 -40 -41 rcurveline
+ -4 -3 -3 -6 -1 -7 -25 -113 -35 -98 -45 -84 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="12">
+ 119 655 -85 callgsubr
+ </CharString>
+ <CharString index="13">
+ 4 -4 5 0 6 2 6 2 3 5 1 7 20 return
+ </CharString>
+ <CharString index="14">
+ -3 -13 4 -15 12 -19 return
+ </CharString>
+ </Subrs>
+ </Private>
+ <CharStrings>
+ <CharString name=".notdef">
+ 111 endchar
+ </CharString>
+ <CharString name="i">
+ 10 -106 callsubr
+ -94 466 -80 callgsubr
+ </CharString>
+ <CharString name="i.TRK">
+ 10 -106 callsubr
+ -94 466 -80 callgsubr
+ </CharString>
+ <CharString name="uni06F0">
+ 332 -84 callgsubr
+ </CharString>
+ <CharString name="uni06F0.numr">
+ 39 -82 callgsubr
+ </CharString>
+ <CharString name="uni06F1">
+ 332 -87 callgsubr
+ </CharString>
+ <CharString name="uni06F1.numr">
+ 39 -95 callsubr
+ </CharString>
+ <CharString name="uni06F2">
+ 332 -97 callgsubr
+ </CharString>
+ <CharString name="uni06F2.numr">
+ 39 -98 callsubr
+ </CharString>
+ <CharString name="uni06F3">
+ 332 -105 callsubr
+ </CharString>
+ <CharString name="uni06F3.numr">
+ 39 263 662 -107 callgsubr
+ </CharString>
+ <CharString name="uni06F4">
+ 332 175 515 rmoveto
+ -12 19 -10 8 -7 -1 -4 -2 -4 -5 -2 -10 -25 -105 rcurveline
+ -93 callsubr
+ 83 -128 46 -149 7 -168 rrcurveto
+ -7 3 -5 4 -4 vhcurveto
+ -94 callsubr
+ 98 -4 110 -26 122 26 -13 33 -5 38 3 63 5 49 28 34 54 9 14 1 10 -7 7 -6 5 -9 -3 -13 -11 rrcurveto
+ -21 -18 -39 -7 -56 3 -38 2 -39 18 -40 34 20 66 50 34 83 1 rrcurveto
+ 26 35 -15 -31 42 hvcurveto
+ 10 -7 6 0 5 7 3 5 -2 9 -7 14 -35 66 -40 33 -44 -3 -76 -5 -57 -50 -40 -93 -4 -11 -5 0 -5 11 -11 28 -19 35 -27 42 rrcurveto
+ endchar
+ </CharString>
+ <CharString name="uni06F4.numr">
+ 39 76 651 rmoveto
+ -12 19 -8 1 -3 -15 -15 -63 rcurveline
+ -2 -7 2 -9 7 -12 50 -77 28 -89 4 -101 rrcurveto
+ -4 2 -3 2 -2 vhcurveto
+ 3 -3 3 0 3 1 rrcurveto
+ 4 2 2 3 4 vvcurveto
+ 12 59 -2 66 -16 73 16 -8 20 -3 22 2 38 3 30 17 20 32 5 8 1 6 -4 5 -4 3 -5 -2 -8 -7 -13 -11 -23 -4 -34 2 -22 1 -24 11 -24 20 rrcurveto
+ 12 40 30 20 50 1 rrcurveto
+ 16 21 -9 -19 25 hvcurveto
+ 6 -4 3 0 3 4 2 3 -1 6 -4 8 -21 40 -24 20 -27 -2 -45 -3 -34 -30 -24 -56 -3 -7 -3 0 -3 7 -6 17 -12 21 -16 25 rrcurveto
+ endchar
+ </CharString>
+ <CharString name="uni06F4.urd">
+ 332 229 -83 rmoveto
+ 31 115 -20 145 -69 177 -16 40 -6 23 3 4 rrcurveto
+ 18 16 19 8 19 hhcurveto
+ 52 -1 42 -43 34 -84 2 -6 3 -3 5 -1 3 -1 3 2 3 5 21 57 34 57 47 57 3 4 3 6 2 7 23 85 rcurveline
+ 1 6 -1 3 -5 2 -4 1 -4 -2 -6 -5 -41 -36 -34 -53 -27 -73 -36 82 -48 40 -59 -3 -53 -2 -36 -32 -19 -62 -20 -65 2 -65 22 -64 49 -133 28 -114 6 -92 rrcurveto
+ -9 1 4 -4 8 hhcurveto
+ 8 5 3 6 2 hvcurveto
+ endchar
+ </CharString>
+ <CharString name="uni06F4.urd.numr">
+ 39 108 303 -102 callsubr
+ </CharString>
+ <CharString name="uni06F5">
+ 332 235 526 rmoveto
+ -12 12 -8 -2 -2 -16 -17 -121 rcurveline
+ -2 -15 3 -8 7 -4 8 -4 8 -5 7 -5 -75 -103 -42 -88 -8 -76 -11 -105 29 -55 68 -6 39 -3 34 17 31 36 16 -26 28 -10 40 7 54 9 33 60 13 110 rrcurveto
+ 5 39 -17 59 -39 78 -29 58 -63 75 -98 92 rrcurveto
+ 16 -184 rmoveto
+ 76 -52 58 -68 42 -83 6 -12 2 -10 -4 -10 -10 -27 -17 -15 -23 -3 -17 -2 -14 3 -12 8 -1 7 1 6 3 5 -1 6 -3 4 -4 2 -5 2 -5 -3 -6 -6 rrcurveto
+ -64 -62 -52 -8 -40 45 -20 23 2 44 25 64 15 38 27 48 41 56 rrcurveto
+ endchar
+ </CharString>
+ <CharString name="uni06F5.numr">
+ 39 112 656 -99 callgsubr
+ </CharString>
+ <CharString name="uni06F6">
+ 332 331 285 rmoveto
+ -74 21 -56 34 -37 47 -5 6 0 5 5 5 45 50 54 25 63 -1 47 -1 34 -12 22 -23 7 -7 6 -1 6 3 5 3 1 5 -3 9 -9 22 -18 26 -26 28 rrcurveto
+ 23 -22 -22 11 -24 hhcurveto
+ -50 -55 -35 -72 -61 hvcurveto
+ -26 -31 -20 -33 -12 -38 -4 -13 1 -14 5 -13 18 -43 52 -37 88 -32 -85 -75 -62 -88 -41 -100 -8 -20 -1 -10 8 -3 10 -4 12 7 11 16 44 66 52 60 58 51 rrcurveto
+ 61 54 61 41 62 26 17 7 12 14 7 21 26 83 rcurveline
+ 6 17 -8 5 -20 -7 -30 -11 -38 -24 -47 -36 -12 -9 -14 -3 -16 5 rrcurveto
+ endchar
+ </CharString>
+ <CharString name="uni06F6.numr">
+ 39 170 512 -104 callsubr
+ </CharString>
+ <CharString name="uni06F6.urd">
+ 332 -90 callgsubr
+ </CharString>
+ <CharString name="uni06F6.urd.numr">
+ 39 -97 callsubr
+ </CharString>
+ <CharString name="uni06F7">
+ 332 -103 callsubr
+ </CharString>
+ <CharString name="uni06F7.numr">
+ 39 -101 callsubr
+ </CharString>
+ <CharString name="uni06F7.urd">
+ 332 104 -50 rmoveto
+ -3 -6 -1 -5 2 -3 2 -3 4 -2 8 1 90 5 83 -7 75 -20 8 -2 6 0 4 2 89 51 rcurveline
+ 18 10 0 8 -18 4 -101 23 -101 5 -100 -15 95 139 69 174 42 210 2 11 0 8 -3 3 -4 4 -5 -2 -6 -6 -68 -68 rcurveline
+ -6 -6 -5 -10 -2 -12 -42 -188 -58 -163 -74 -140 rrcurveto
+ endchar
+ </CharString>
+ <CharString name="uni06F7.urd.numr">
+ 39 33 312 rmoveto
+ -8 -4 3 -3 9 -96 callsubr
+ </CharString>
+ <CharString name="uni06F8">
+ 332 -98 callgsubr
+ </CharString>
+ <CharString name="uni06F8.numr">
+ 39 -100 callsubr
+ </CharString>
+ <CharString name="uni06F9">
+ 332 -99 callsubr
+ </CharString>
+ <CharString name="uni06F9.numr">
+ 39 -93 callgsubr
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ <CharString index="0">
+ rmoveto
+ -3 -50 -12 -21 -23 6 -22 6 -9 11 3 15 3 15 0 10 -1 5 -1 5 -3 3 -5 1 rrcurveto
+ -5 -2 -4 -8 -2 hvcurveto
+ -8 -41 -16 -22 -24 -2 -17 -2 -14 2 -12 7 -4 3 -15 19 -23 34 -10 15 -8 0 -3 -13 -15 -61 rcurveline
+ -2 -8 3 -9 8 -12 48 -71 27 -88 6 -107 1 -5 1 -4 3 -1 2 -2 2 0 4 1 4 1 1 3 2 4 12 47 -7 74 -25 101 23 -1 21 9 19 21 rrcurveto
+ 9 -6 10 -6 12 -3 38 -12 24 13 11 39 7 30 5 27 1 25 rrcurveto
+ 12 -3 5 -6 -6 -3 -3 -7 -1 vhcurveto
+ endchar
+ </CharString>
+ <CharString index="1">
+ rmoveto
+ 237 vlineto
+ -92 callgsubr
+ return
+ </CharString>
+ <CharString index="2">
+ vlineto
+ -29 0 -27 -5 -16 -102 callgsubr
+ -46 -77 callgsubr
+ vvcurveto
+ return
+ </CharString>
+ <CharString index="3">
+ rmoveto
+ -100 callgsubr
+ return
+ </CharString>
+ <CharString index="4">
+ -81 callgsubr
+ 49 0 -17 7 hvcurveto
+ 5 -12 0 return
+ </CharString>
+ <CharString index="5">
+ vhcurveto
+ -11 -3 -47 -74 callgsubr
+ 26 -79 callgsubr
+ 26 -78 callgsubr
+ return
+ </CharString>
+ <CharString index="6">
+ 5 5 -1 18 -4 5 rrcurveto
+ -12 return
+ </CharString>
+ <CharString index="7">
+ -27 22 -22 27 27 22 22 27 27 -22 22 -27 -27 -22 -22 -27 vhcurveto
+ return
+ </CharString>
+ <CharString index="8">
+ rmoveto
+ -7 7 -5 -1 -1 -10 -10 -73 rcurveline
+ -2 -9 2 -4 4 -3 5 -2 5 -3 4 -3 -45 -62 -25 -53 -5 -45 -6 -63 17 -33 41 -4 23 -2 21 10 18 22 10 -16 17 -6 24 5 32 5 20 36 8 66 rrcurveto
+ 3 23 -11 36 -23 47 -17 34 -38 45 -59 56 rrcurveto
+ 10 -111 rmoveto
+ 45 -31 35 -41 25 -50 4 -7 1 -6 -2 -6 -6 -16 -11 -9 -13 -2 -11 -1 -8 2 -7 5 -1 4 1 3 2 3 -1 4 -2 2 -2 2 -3 1 -3 -2 -4 -4 rrcurveto
+ -38 -37 -31 -5 -24 27 -12 14 1 27 15 38 9 23 16 29 25 33 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="9">
+ 275 527 rmoveto
+ -21 -89 -27 -81 -29 -73 -29 -73 -38 -73 -45 -76 rrcurveto
+ -6 -11 -2 -10 -8 vvcurveto
+ 5 -97 rlineto
+ -9 1 3 -3 5 hhcurveto
+ 5 5 5 10 5 hvcurveto
+ 79 149 53 135 27 121 4 19 7 -2 7 -21 55 -159 61 -140 70 -124 11 -20 8 8 5 35 13 81 rcurveline
+ 2 10 -1 8 -3 6 -57 95 -46 93 -38 90 -38 90 -20 68 -2 45 rrcurveto
+ 7 -3 5 -4 3 vhcurveto
+ -4 3 -4 0 -5 -2 -5 -2 -2 -6 -2 -7 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="10">
+ 137 -96 callgsubr
+ </CharString>
+ <CharString index="11">
+ 521 rmoveto
+ -25 -102 -3 -13 4 -15 12 -19 rlinecurve
+ 79 -124 46 -149 11 -172 1 -7 2 -5 4 -4 4 -4 5 0 6 2 6 2 3 5 1 7 20 99 -12 128 -42 158 79 -13 57 18 33 49 21 30 13 33 6 37 rrcurveto
+ 6 37 3 24 -3 7 rrcurveto
+ 7 -3 -6 4 -9 hhcurveto
+ -9 -6 -6 -12 -2 hvcurveto
+ -69 -9 -43 -36 -79 hhcurveto
+ -55 -44 32 62 -33 hvcurveto
+ -11 21 -9 10 -7 -2 -5 -1 -4 -6 -3 -13 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="12">
+ rmoveto
+ 12 53 16 49 17 44 18 43 23 45 27 45 rrcurveto
+ 4 7 1 5 5 vvcurveto
+ -4 58 rlineto
+ 6 -2 2 -3 -3 -3 -3 -6 -3 vhcurveto
+ -47 -90 -32 -81 -16 -72 -2 -12 -5 1 -4 13 -33 95 -36 84 -42 75 -7 12 -5 -5 -3 -21 -8 -49 rcurveline
+ -1 -6 1 -4 2 -4 34 -57 27 -56 23 -54 23 -54 12 -41 1 -27 rrcurveto
+ -4 2 -2 2 -2 vhcurveto
+ 3 -2 2 0 3 1 3 2 1 3 2 4 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="13">
+ rmoveto
+ -15 -62 -2 -7 3 -9 7 -12 rlinecurve
+ 47 -74 28 -90 7 -103 rrcurveto
+ -4 1 -3 3 -2 vhcurveto
+ 2 -3 3 0 4 1 3 2 2 3 1 4 12 59 -7 77 -26 95 48 -8 34 11 20 29 12 18 8 20 4 22 3 22 2 15 -2 4 rrcurveto
+ 4 -1 -4 3 -5 hhcurveto
+ -6 -3 -4 -7 -2 hvcurveto
+ -42 -5 -26 -21 -47 hhcurveto
+ -33 -27 19 37 -19 hvcurveto
+ -7 13 -5 6 -5 -1 -3 -1 -2 -4 -2 -7 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="14">
+ 103 641 -91 callgsubr
+ </CharString>
+ <CharString index="15">
+ 10 6 47 1 7 vhcurveto
+ 5 -6 11 -5 vhcurveto
+ -39 -21 -50 -103 callgsubr
+ -26 -13 vvcurveto
+ -182 -105 callgsubr
+ return
+ </CharString>
+ <CharString index="16">
+ rmoveto
+ -40 -52 -19 -39 1 -28 2 -52 45 -21 88 8 3 -49 13 -52 23 -55 9 -21 6 -1 3 20 11 63 rcurveline
+ 2 9 -2 10 -6 10 -19 36 -11 44 -2 51 -1 52 -10 40 -19 27 -24 34 -25 0 -28 -34 rrcurveto
+ 61 -91 rmoveto
+ -16 5 -2 -9 -10 hhcurveto
+ -30 -2 -22 3 -13 9 -9 6 -3 9 4 13 7 23 14 9 22 -6 18 -5 13 -13 6 -21 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="17">
+ 111 -89 callgsubr
+ </CharString>
+ <CharString index="18">
+ 522 rmoveto
+ -15 -79 -3 -13 4 -12 9 -10 rlinecurve
+ 50 -56 78 -14 105 27 rrcurveto
+ 1 1 0 0 hvcurveto
+ 3 -1 1 -4 -7 vvcurveto
+ -5 -150 31 -142 70 -132 15 -29 11 3 6 32 17 95 rcurveline
+ 3 17 -4 18 -9 17 -71 128 -29 128 13 130 2 16 -8 6 -18 -5 -109 -33 -75 12 -38 57 -19 28 -12 0 -5 -27 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="19">
+ rmoveto
+ -9 -47 -2 -8 2 -7 6 -6 rlinecurve
+ 30 -34 46 -8 63 16 rrcurveto
+ 3 1 1 -3 -5 vvcurveto
+ -3 -90 19 -85 42 -80 9 -17 6 2 4 19 10 57 rcurveline
+ 2 10 -3 11 -5 10 -43 77 -17 77 8 78 1 9 -5 4 -11 -3 -65 -20 -45 7 -23 35 -11 16 -7 0 -3 -16 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="20">
+ 246 -86 callgsubr
+ </CharString>
+ <CharString index="21">
+ 524 rmoveto
+ -34 -100 -5 -14 3 -17 11 -20 rlinecurve
+ 71 -126 35 -148 -2 -171 rrcurveto
+ -12 4 -6 9 -2 vhcurveto
+ 9 -2 5 5 4 10 23 69 4 99 -18 129 -13 92 -27 96 -39 100 -17 40 -14 6 -9 -28 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="22">
+ rmoveto
+ -21 -60 -3 -8 2 -10 7 -12 rlinecurve
+ 42 -76 21 -89 -1 -102 rrcurveto
+ -7 2 -4 6 -1 vhcurveto
+ 5 -1 3 3 3 6 13 41 3 59 -11 78 -8 55 -16 58 -23 60 -11 24 -8 3 -5 -17 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="23">
+ 251 -83 callgsubr
+ </CharString>
+ <CharString index="24">
+ 312 rmoveto
+ -38 -106 -5 -15 1 -10 8 -5 rlinecurve
+ 91 -53 17 -10 11 2 6 17 rlinecurve
+ 32 99 5 15 -3 11 -12 8 rlinecurve
+ -83 53 -9 6 -7 2 -5 -3 rlinecurve
+ -4 -1 -3 -4 -2 -6 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="25">
+ 122 527 rmoveto
+ -23 -63 -3 -9 0 -6 5 -3 rlinecurve
+ 55 -32 10 -6 7 1 3 10 rlinecurve
+ 19 60 3 9 -1 6 -8 5 rlinecurve
+ -49 32 -9 6 -6 -1 -3 -9 rlinecurve
+ endchar
+ </CharString>
+ <CharString index="26">
+ -13 -50 -7 -4 -5 2 -19 2 -2 rrcurveto
+ 7 return
+ </CharString>
+ <CharString index="27">
+ -104 callgsubr
+ endchar
+ </CharString>
+ <CharString index="28">
+ 3 38 hhcurveto
+ 38 return
+ </CharString>
+ <CharString index="29">
+ -3 -2 40 hvcurveto
+ -101 callgsubr
+ return
+ </CharString>
+ <CharString index="30">
+ 5 11 -3 hvcurveto
+ -5 16 0 27 29 return
+ </CharString>
+ <CharString index="31">
+ 5 -5 rrcurveto
+ 2 40 return
+ </CharString>
+ <CharString index="32">
+ -5 -12 hhcurveto
+ -4 -5 -1 return
+ </CharString>
+ <CharString index="33">
+ -75 callgsubr
+ -18 -76 callgsubr
+ return
+ </CharString>
+ </GlobalSubrs>
+ </CFF>
+
+ <GDEF>
+ <Version value="0x00010000"/>
+ <GlyphClassDef>
+ <ClassDef glyph="i" class="1"/>
+ <ClassDef glyph="i.TRK" class="1"/>
+ <ClassDef glyph="uni06F0" class="1"/>
+ <ClassDef glyph="uni06F0.numr" class="1"/>
+ <ClassDef glyph="uni06F1" class="1"/>
+ <ClassDef glyph="uni06F1.numr" class="1"/>
+ <ClassDef glyph="uni06F2" class="1"/>
+ <ClassDef glyph="uni06F2.numr" class="1"/>
+ <ClassDef glyph="uni06F3" class="1"/>
+ <ClassDef glyph="uni06F3.numr" class="1"/>
+ <ClassDef glyph="uni06F4" class="1"/>
+ <ClassDef glyph="uni06F4.numr" class="1"/>
+ <ClassDef glyph="uni06F4.urd" class="1"/>
+ <ClassDef glyph="uni06F4.urd.numr" class="1"/>
+ <ClassDef glyph="uni06F5" class="1"/>
+ <ClassDef glyph="uni06F5.numr" class="1"/>
+ <ClassDef glyph="uni06F6" class="1"/>
+ <ClassDef glyph="uni06F6.numr" class="1"/>
+ <ClassDef glyph="uni06F6.urd" class="1"/>
+ <ClassDef glyph="uni06F6.urd.numr" class="1"/>
+ <ClassDef glyph="uni06F7" class="1"/>
+ <ClassDef glyph="uni06F7.numr" class="1"/>
+ <ClassDef glyph="uni06F7.urd" class="1"/>
+ <ClassDef glyph="uni06F7.urd.numr" class="1"/>
+ <ClassDef glyph="uni06F8" class="1"/>
+ <ClassDef glyph="uni06F8.numr" class="1"/>
+ <ClassDef glyph="uni06F9" class="1"/>
+ <ClassDef glyph="uni06F9.numr" class="1"/>
+ </GlyphClassDef>
+ </GDEF>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=0 -->
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=0 -->
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=0 -->
+ </LookupList>
+ </GPOS>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=3 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="4"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ <ScriptRecord index="1">
+ <ScriptTag value="arab"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="4"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=3 -->
+ <LangSysRecord index="0">
+ <LangSysTag value="KSH "/>
+ <LangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="0"/>
+ <FeatureIndex index="1" value="4"/>
+ </LangSys>
+ </LangSysRecord>
+ <LangSysRecord index="1">
+ <LangSysTag value="SND "/>
+ <LangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="1"/>
+ <FeatureIndex index="1" value="4"/>
+ </LangSys>
+ </LangSysRecord>
+ <LangSysRecord index="2">
+ <LangSysTag value="URD "/>
+ <LangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="3"/>
+ <FeatureIndex index="1" value="4"/>
+ </LangSys>
+ </LangSysRecord>
+ </Script>
+ </ScriptRecord>
+ <ScriptRecord index="2">
+ <ScriptTag value="latn"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="4"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=1 -->
+ <LangSysRecord index="0">
+ <LangSysTag value="TRK "/>
+ <LangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="2"/>
+ <FeatureIndex index="1" value="4"/>
+ </LangSys>
+ </LangSysRecord>
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=5 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="locl"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="locl"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="2"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="2">
+ <FeatureTag value="locl"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="4"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="3">
+ <FeatureTag value="locl"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="4">
+ <FeatureTag value="numr"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="3"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=5 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni06F4" out="uni06F4.urd"/>
+ <Substitution in="uni06F6" out="uni06F6.urd"/>
+ <Substitution in="uni06F7" out="uni06F7.urd"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni06F4" out="uni06F4.urd"/>
+ <Substitution in="uni06F6" out="uni06F6.urd"/>
+ <Substitution in="uni06F7" out="uni06F7.urd"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni06F6" out="uni06F6.urd"/>
+ <Substitution in="uni06F7" out="uni06F7.urd"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="3">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni06F0" out="uni06F0.numr"/>
+ <Substitution in="uni06F1" out="uni06F1.numr"/>
+ <Substitution in="uni06F2" out="uni06F2.numr"/>
+ <Substitution in="uni06F3" out="uni06F3.numr"/>
+ <Substitution in="uni06F4" out="uni06F4.numr"/>
+ <Substitution in="uni06F4.urd" out="uni06F4.urd.numr"/>
+ <Substitution in="uni06F5" out="uni06F5.numr"/>
+ <Substitution in="uni06F6" out="uni06F6.numr"/>
+ <Substitution in="uni06F6.urd" out="uni06F6.urd.numr"/>
+ <Substitution in="uni06F7" out="uni06F7.numr"/>
+ <Substitution in="uni06F7.urd" out="uni06F7.urd.numr"/>
+ <Substitution in="uni06F8" out="uni06F8.numr"/>
+ <Substitution in="uni06F9" out="uni06F9.numr"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="4">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="i" out="i.TRK"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+ <hmtx>
+ <mtx name=".notdef" width="364" lsb="33"/>
+ <mtx name="i" width="263" lsb="32"/>
+ <mtx name="i.TRK" width="263" lsb="32"/>
+ <mtx name="uni06F0" width="585" lsb="210"/>
+ <mtx name="uni06F0.numr" width="292" lsb="97"/>
+ <mtx name="uni06F1" width="585" lsb="210"/>
+ <mtx name="uni06F1.numr" width="292" lsb="97"/>
+ <mtx name="uni06F2" width="585" lsb="111"/>
+ <mtx name="uni06F2.numr" width="292" lsb="37"/>
+ <mtx name="uni06F3" width="585" lsb="67"/>
+ <mtx name="uni06F3.numr" width="292" lsb="11"/>
+ <mtx name="uni06F4" width="585" lsb="110"/>
+ <mtx name="uni06F4.numr" width="292" lsb="37"/>
+ <mtx name="uni06F4.urd" width="585" lsb="100"/>
+ <mtx name="uni06F4.urd.numr" width="292" lsb="31"/>
+ <mtx name="uni06F5" width="585" lsb="100"/>
+ <mtx name="uni06F5.numr" width="292" lsb="31"/>
+ <mtx name="uni06F6" width="585" lsb="71"/>
+ <mtx name="uni06F6.numr" width="292" lsb="14"/>
+ <mtx name="uni06F6.urd" width="585" lsb="95"/>
+ <mtx name="uni06F6.urd.numr" width="292" lsb="28"/>
+ <mtx name="uni06F7" width="585" lsb="78"/>
+ <mtx name="uni06F7.numr" width="292" lsb="18"/>
+ <mtx name="uni06F7.urd" width="585" lsb="101"/>
+ <mtx name="uni06F7.urd.numr" width="292" lsb="31"/>
+ <mtx name="uni06F8" width="585" lsb="78"/>
+ <mtx name="uni06F8.numr" width="292" lsb="18"/>
+ <mtx name="uni06F9" width="585" lsb="123"/>
+ <mtx name="uni06F9.numr" width="292" lsb="45"/>
+ </hmtx>
+
+</ttFont>
diff --git a/Tests/subset/data/test_cntrmask_CFF.ttx b/Tests/subset/data/test_cntrmask_CFF.ttx
index 5ab6268f..9e7d2051 100644
--- a/Tests/subset/data/test_cntrmask_CFF.ttx
+++ b/Tests/subset/data/test_cntrmask_CFF.ttx
@@ -237,13 +237,13 @@
<GDEF>
<Version value="0x00010002"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="Idieresis" class="1"/>
</GlyphClassDef>
<MarkGlyphSetsDef>
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=1 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
</Coverage>
</MarkGlyphSetsDef>
</GDEF>
@@ -335,15 +335,15 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="Idieresis"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="Idieresis" class="1"/>
</ClassDef1>
- <ClassDef2 Format="2">
+ <ClassDef2>
</ClassDef2>
<!-- Class1Count=2 -->
<!-- Class2Count=1 -->
diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py
index 9495f326..6fa1bf60 100644
--- a/Tests/subset/subset_test.py
+++ b/Tests/subset/subset_test.py
@@ -1,7 +1,11 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+import io
+from fontTools.misc.py23 import tobytes, tostr
+from fontTools.misc.testTools import getXML
from fontTools import subset
+from fontTools.fontBuilder import FontBuilder
+from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.ttLib import TTFont, newTable
+from fontTools.ttLib.tables import otTables as ot
from fontTools.misc.loggingTools import CapturingLogHandler
import difflib
import logging
@@ -10,6 +14,8 @@ import shutil
import sys
import tempfile
import unittest
+import pathlib
+import pytest
class SubsetTest(unittest.TestCase):
@@ -52,7 +58,7 @@ class SubsetTest(unittest.TestCase):
lines.append(line.rstrip() + os.linesep)
return lines
- def expect_ttx(self, font, expected_ttx, tables):
+ def expect_ttx(self, font, expected_ttx, tables=None):
path = self.temp_path(suffix=".ttx")
font.saveXML(path, tables=tables)
actual = self.read_ttx(path)
@@ -74,6 +80,16 @@ class SubsetTest(unittest.TestCase):
# Tests
# -----
+ def test_layout_scripts(self):
+ _, fontpath = self.compile_font(self.getpath("layout_scripts.ttx"), ".otf")
+ subsetpath = self.temp_path(".otf")
+ subset.main([fontpath, "--glyphs=*", "--layout-features=*",
+ "--layout-scripts=latn,arab.URD,arab.dflt",
+ "--output-file=%s" % subsetpath])
+ subsetfont = TTFont(subsetpath)
+ self.expect_ttx(subsetfont, self.getpath("expect_layout_scripts.ttx"),
+ ["GPOS", "GSUB"])
+
def test_no_notdef_outline_otf(self):
_, fontpath = self.compile_font(self.getpath("TestOTF-Regular.ttx"), ".otf")
subsetpath = self.temp_path(".otf")
@@ -728,6 +744,475 @@ class SubsetTest(unittest.TestCase):
self.assertEqual(ttf.flavor, None)
+ def test_subset_context_subst_format_3(self):
+ # https://github.com/fonttools/fonttools/issues/1879
+ # Test font contains 'calt' feature with Format 3 ContextSubst lookup subtables
+ ttx = self.getpath("TestContextSubstFormat3.ttx")
+ font, fontpath = self.compile_font(ttx, ".ttf")
+ subsetpath = self.temp_path(".ttf")
+ subset.main([fontpath, "--unicodes=*", "--output-file=%s" % subsetpath])
+ subsetfont = TTFont(subsetpath)
+ # check all glyphs are kept via GSUB closure, no changes expected
+ self.expect_ttx(subsetfont, ttx)
+
+ def test_cmap_prune_format12(self):
+ _, fontpath = self.compile_font(self.getpath("CmapSubsetTest.ttx"), ".ttf")
+ subsetpath = self.temp_path(".ttf")
+ subset.main([fontpath, "--glyphs=a", "--output-file=%s" % subsetpath])
+ subsetfont = TTFont(subsetpath)
+ self.expect_ttx(subsetfont, self.getpath("CmapSubsetTest.subset.ttx"), ["cmap"])
+
+ def test_GPOS_PairPos_Format2_useClass0(self):
+ # Check two things related to class 0 ('every other glyph'):
+ # 1) that it's reused for ClassDef1 when it becomes empty as the subset glyphset
+ # is intersected with the table's Coverage
+ # 2) that it is never reused for ClassDef2 even when it happens to become empty
+ # because of the subset glyphset. In this case, we don't keep a PairPosClass2
+ # subtable if only ClassDef2's class0 survived subsetting.
+ # The test font (from Harfbuzz test suite) is constructed to trigger these two
+ # situations depending on the input subset --text.
+ # https://github.com/fonttools/fonttools/pull/2221
+ _, fontpath = self.compile_font(
+ self.getpath("GPOS_PairPos_Format2_PR_2221.ttx"), ".ttf"
+ )
+ subsetpath = self.temp_path(".ttf")
+
+ for n, text in enumerate("!#", start=1):
+ expected_ttx = self.getpath(
+ f"GPOS_PairPos_Format2_ClassDef{n}_useClass0.subset.ttx"
+ )
+ with self.subTest(text=text, expected_ttx=expected_ttx):
+ subset.main(
+ [
+ fontpath,
+ f"--text='{text}'",
+ "--layout-features+=test",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
+ subsetfont = TTFont(subsetpath)
+ self.expect_ttx(subsetfont, expected_ttx, ["GPOS"])
+
+
+@pytest.fixture
+def featureVarsTestFont():
+ fb = FontBuilder(unitsPerEm=100)
+ fb.setupGlyphOrder([".notdef", "f", "f_f", "dollar", "dollar.rvrn"])
+ fb.setupCharacterMap({ord("f"): "f", ord("$"): "dollar"})
+ fb.setupNameTable({"familyName": "TestFeatureVars", "styleName": "Regular"})
+ fb.setupPost()
+ fb.setupFvar(axes=[("wght", 100, 400, 900, "Weight")], instances=[])
+ fb.addOpenTypeFeatures("""\
+ feature dlig {
+ sub f f by f_f;
+ } dlig;
+ """)
+ fb.addFeatureVariations(
+ [([{"wght": (0.20886, 1.0)}], {"dollar": "dollar.rvrn"})],
+ featureTag="rvrn"
+ )
+ buf = io.BytesIO()
+ fb.save(buf)
+ buf.seek(0)
+
+ return TTFont(buf)
+
+
+def test_subset_feature_variations_keep_all(featureVarsTestFont):
+ font = featureVarsTestFont
+
+ options = subset.Options()
+ subsetter = subset.Subsetter(options)
+ subsetter.populate(unicodes=[ord("f"), ord("$")])
+ subsetter.subset(font)
+
+ featureTags = {
+ r.FeatureTag for r in font["GSUB"].table.FeatureList.FeatureRecord
+ }
+ # 'dlig' is discretionary so it is dropped by default
+ assert "dlig" not in featureTags
+ assert "f_f" not in font.getGlyphOrder()
+ # 'rvrn' is required so it is kept by default
+ assert "rvrn" in featureTags
+ assert "dollar.rvrn" in font.getGlyphOrder()
+
+
+def test_subset_feature_variations_drop_all(featureVarsTestFont):
+ font = featureVarsTestFont
+
+ options = subset.Options()
+ options.layout_features.remove("rvrn") # drop 'rvrn'
+ subsetter = subset.Subsetter(options)
+ subsetter.populate(unicodes=[ord("f"), ord("$")])
+ subsetter.subset(font)
+
+ featureTags = {
+ r.FeatureTag for r in font["GSUB"].table.FeatureList.FeatureRecord
+ }
+ glyphs = set(font.getGlyphOrder())
+
+ assert "rvrn" not in featureTags
+ assert glyphs == {".notdef", "f", "dollar"}
+ # all FeatureVariationRecords were dropped
+ assert font["GSUB"].table.FeatureVariations is None
+ assert font["GSUB"].table.Version == 0x00010000
+
+
+# TODO test_subset_feature_variations_drop_from_end_empty_records
+# https://github.com/fonttools/fonttools/issues/1881#issuecomment-619415044
+
+
+def test_subset_single_pos_format():
+ fb = FontBuilder(unitsPerEm=1000)
+ fb.setupGlyphOrder([".notdef", "a", "b", "c"])
+ fb.setupCharacterMap({ord("a"): "a", ord("b"): "b", ord("c"): "c"})
+ fb.setupNameTable({"familyName": "TestSingePosFormat", "styleName": "Regular"})
+ fb.setupPost()
+ fb.addOpenTypeFeatures("""
+ feature kern {
+ pos a -50;
+ pos b -40;
+ pos c -50;
+ } kern;
+ """)
+
+ buf = io.BytesIO()
+ fb.save(buf)
+ buf.seek(0)
+
+ font = TTFont(buf)
+
+ # The input font has a SinglePos Format 2 subtable where each glyph has
+ # different ValueRecords
+ assert getXML(font["GPOS"].table.LookupList.Lookup[0].toXML, font) == [
+ '<Lookup>',
+ ' <LookupType value="1"/>',
+ ' <LookupFlag value="0"/>',
+ ' <!-- SubTableCount=1 -->',
+ ' <SinglePos index="0" Format="2">',
+ ' <Coverage>',
+ ' <Glyph value="a"/>',
+ ' <Glyph value="b"/>',
+ ' <Glyph value="c"/>',
+ ' </Coverage>',
+ ' <ValueFormat value="4"/>',
+ ' <!-- ValueCount=3 -->',
+ ' <Value index="0" XAdvance="-50"/>',
+ ' <Value index="1" XAdvance="-40"/>',
+ ' <Value index="2" XAdvance="-50"/>',
+ ' </SinglePos>',
+ '</Lookup>',
+ ]
+
+ options = subset.Options()
+ subsetter = subset.Subsetter(options)
+ subsetter.populate(unicodes=[ord("a"), ord("c")])
+ subsetter.subset(font)
+
+ # All the subsetted glyphs from the original SinglePos Format2 subtable
+ # now have the same ValueRecord, so we use a more compact Format 1 subtable.
+ assert getXML(font["GPOS"].table.LookupList.Lookup[0].toXML, font) == [
+ '<Lookup>',
+ ' <LookupType value="1"/>',
+ ' <LookupFlag value="0"/>',
+ ' <!-- SubTableCount=1 -->',
+ ' <SinglePos index="0" Format="1">',
+ ' <Coverage>',
+ ' <Glyph value="a"/>',
+ ' <Glyph value="c"/>',
+ ' </Coverage>',
+ ' <ValueFormat value="4"/>',
+ ' <Value XAdvance="-50"/>',
+ ' </SinglePos>',
+ '</Lookup>',
+ ]
+
+
+@pytest.fixture
+def ttf_path(tmp_path):
+ # $(dirname $0)/../ttLib/data
+ ttLib_data = pathlib.Path(__file__).parent.parent / "ttLib" / "data"
+ font = TTFont()
+ font.importXML(ttLib_data / "TestTTF-Regular.ttx")
+ font_path = tmp_path / "TestTTF-Regular.ttf"
+ font.save(font_path)
+ return font_path
+
+
+def test_subset_empty_glyf(tmp_path, ttf_path):
+ subset_path = tmp_path / (ttf_path.name + ".subset")
+ # only keep empty .notdef and space glyph, resulting in an empty glyf table
+ subset.main(
+ [
+ str(ttf_path),
+ "--no-notdef-outline",
+ "--glyph-names",
+ f"--output-file={subset_path}",
+ "--glyphs=.notdef space",
+ ]
+ )
+ subset_font = TTFont(subset_path)
+
+ assert subset_font.getGlyphOrder() == [".notdef", "space"]
+ assert subset_font.reader['glyf'] == b"\x00"
+
+ glyf = subset_font["glyf"]
+ assert all(glyf[g].numberOfContours == 0 for g in subset_font.getGlyphOrder())
+
+ loca = subset_font["loca"]
+ assert all(loc == 0 for loc in loca)
+
+
+@pytest.fixture
+def colrv1_path(tmp_path):
+ base_glyph_names = ["uni%04X" % i for i in range(0xE000, 0xE000 + 10)]
+ layer_glyph_names = ["glyph%05d" % i for i in range(10, 20)]
+ glyph_order = [".notdef"] + base_glyph_names + layer_glyph_names
+
+ pen = TTGlyphPen(glyphSet=None)
+ pen.moveTo((0, 0))
+ pen.lineTo((0, 500))
+ pen.lineTo((500, 500))
+ pen.lineTo((500, 0))
+ pen.closePath()
+ glyph = pen.glyph()
+ glyphs = {g: glyph for g in glyph_order}
+
+ fb = FontBuilder(unitsPerEm=1024, isTTF=True)
+ fb.setupGlyphOrder(glyph_order)
+ fb.setupCharacterMap({int(name[3:], 16): name for name in base_glyph_names})
+ fb.setupGlyf(glyphs)
+ fb.setupHorizontalMetrics({g: (500, 0) for g in glyph_order})
+ fb.setupHorizontalHeader()
+ fb.setupOS2()
+ fb.setupPost()
+ fb.setupNameTable({"familyName": "TestCOLRv1", "styleName": "Regular"})
+
+ fb.setupCOLR(
+ {
+ "uniE000": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": (ot.PaintFormat.PaintSolid, 0),
+ "Glyph": "glyph00010",
+ },
+ {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": (ot.PaintFormat.PaintSolid, (2, 0.3)),
+ "Glyph": "glyph00011",
+ },
+ ],
+ ),
+ "uniE001": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ {
+ "Format": ot.PaintFormat.PaintTransform,
+ "Paint": {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": {
+ "Format": ot.PaintFormat.PaintRadialGradient,
+ "x0": 250,
+ "y0": 250,
+ "r0": 250,
+ "x1": 200,
+ "y1": 200,
+ "r1": 0,
+ "ColorLine": {
+ "ColorStop": [(0.0, 1), (1.0, 2)],
+ "Extend": "repeat",
+ },
+ },
+ "Glyph": "glyph00012",
+ },
+ "Transform": (0.7071, 0.7071, -0.7071, 0.7071, 0, 0),
+ },
+ {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": (ot.PaintFormat.PaintSolid, (1, 0.5)),
+ "Glyph": "glyph00013",
+ },
+ ],
+ ),
+ "uniE002": (
+ ot.PaintFormat.PaintColrLayers,
+ [
+ {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": {
+ "Format": ot.PaintFormat.PaintLinearGradient,
+ "x0": 0,
+ "y0": 0,
+ "x1": 500,
+ "y1": 500,
+ "x2": -500,
+ "y2": 500,
+ "ColorLine": {"ColorStop": [(0.0, 1), (1.0, 2)]},
+ },
+ "Glyph": "glyph00014",
+ },
+ {
+ "Format": ot.PaintFormat.PaintTransform,
+ "Paint": {
+ "Format": ot.PaintFormat.PaintGlyph,
+ "Paint": (ot.PaintFormat.PaintSolid, 1),
+ "Glyph": "glyph00015",
+ },
+ "Transform": (1, 0, 0, 1, 400, 400),
+ },
+ ],
+ ),
+ "uniE003": {
+ "Format": ot.PaintFormat.PaintRotate,
+ "Paint": {
+ "Format": ot.PaintFormat.PaintColrGlyph,
+ "Glyph": "uniE001",
+ },
+ "angle": 45,
+ "centerX": 250,
+ "centerY": 250,
+ },
+ "uniE004": [
+ ("glyph00016", 1),
+ ("glyph00017", 2),
+ ],
+ },
+ )
+ fb.setupCPAL(
+ [
+ [
+ (1.0, 0.0, 0.0, 1.0), # red
+ (0.0, 1.0, 0.0, 1.0), # green
+ (0.0, 0.0, 1.0, 1.0), # blue
+ ],
+ ],
+ )
+
+ output_path = tmp_path / "TestCOLRv1.ttf"
+ fb.save(output_path)
+
+ return output_path
+
+
+def test_subset_COLRv1_and_CPAL(colrv1_path):
+ subset_path = colrv1_path.parent / (colrv1_path.name + ".subset")
+
+ subset.main(
+ [
+ str(colrv1_path),
+ "--glyph-names",
+ f"--output-file={subset_path}",
+ "--unicodes=E002,E003,E004",
+ ]
+ )
+ subset_font = TTFont(subset_path)
+
+ glyph_set = set(subset_font.getGlyphOrder())
+
+ # uniE000 and its children are excluded from subset
+ assert "uniE000" not in glyph_set
+ assert "glyph00010" not in glyph_set
+ assert "glyph00011" not in glyph_set
+
+ # uniE001 and children are pulled in indirectly as PaintColrGlyph by uniE003
+ assert "uniE001" in glyph_set
+ assert "glyph00012" in glyph_set
+ assert "glyph00013" in glyph_set
+
+ assert "uniE002" in glyph_set
+ assert "glyph00014" in glyph_set
+ assert "glyph00015" in glyph_set
+
+ assert "uniE003" in glyph_set
+
+ assert "uniE004" in glyph_set
+ assert "glyph00016" in glyph_set
+ assert "glyph00017" in glyph_set
+
+ assert "COLR" in subset_font
+ colr = subset_font["COLR"].table
+ assert colr.Version == 1
+ assert len(colr.BaseGlyphRecordArray.BaseGlyphRecord) == 1
+ assert len(colr.BaseGlyphV1List.BaseGlyphV1Record) == 3 # was 4
+
+ base = colr.BaseGlyphV1List.BaseGlyphV1Record[0]
+ assert base.BaseGlyph == "uniE001"
+ layers = colr.LayerV1List.Paint[
+ base.Paint.FirstLayerIndex: base.Paint.FirstLayerIndex + base.Paint.NumLayers
+ ]
+ assert len(layers) == 2
+ # check v1 palette indices were remapped
+ assert layers[0].Paint.Paint.ColorLine.ColorStop[0].Color.PaletteIndex == 0
+ assert layers[0].Paint.Paint.ColorLine.ColorStop[1].Color.PaletteIndex == 1
+ assert layers[1].Paint.Color.PaletteIndex == 0
+
+ baseRecV0 = colr.BaseGlyphRecordArray.BaseGlyphRecord[0]
+ assert baseRecV0.BaseGlyph == "uniE004"
+ layersV0 = colr.LayerRecordArray.LayerRecord
+ assert len(layersV0) == 2
+ # check v0 palette indices were remapped
+ assert layersV0[0].PaletteIndex == 0
+ assert layersV0[1].PaletteIndex == 1
+
+ assert "CPAL" in subset_font
+ cpal = subset_font["CPAL"]
+ assert [
+ tuple(v / 255 for v in (c.red, c.green, c.blue, c.alpha))
+ for c in cpal.palettes[0]
+ ] == [
+ # the first color 'red' was pruned
+ (0.0, 1.0, 0.0, 1.0), # green
+ (0.0, 0.0, 1.0, 1.0), # blue
+ ]
+
+
+def test_subset_COLRv1_and_CPAL_drop_empty(colrv1_path):
+ subset_path = colrv1_path.parent / (colrv1_path.name + ".subset")
+
+ subset.main(
+ [
+ str(colrv1_path),
+ "--glyph-names",
+ f"--output-file={subset_path}",
+ "--glyphs=glyph00010",
+ ]
+ )
+ subset_font = TTFont(subset_path)
+
+ glyph_set = set(subset_font.getGlyphOrder())
+
+ assert "glyph00010" in glyph_set
+ assert "uniE000" not in glyph_set
+
+ assert "COLR" not in subset_font
+ assert "CPAL" not in subset_font
+
+
+def test_subset_COLRv1_downgrade_version(colrv1_path):
+ subset_path = colrv1_path.parent / (colrv1_path.name + ".subset")
+
+ subset.main(
+ [
+ str(colrv1_path),
+ "--glyph-names",
+ f"--output-file={subset_path}",
+ "--unicodes=E004",
+ ]
+ )
+ subset_font = TTFont(subset_path)
+
+ assert set(subset_font.getGlyphOrder()) == {
+ ".notdef",
+ "uniE004",
+ "glyph00016",
+ "glyph00017",
+ }
+
+ assert "COLR" in subset_font
+ assert subset_font["COLR"].version == 0
+
if __name__ == "__main__":
sys.exit(unittest.main())
diff --git a/Tests/svgLib/path/parser_test.py b/Tests/svgLib/path/parser_test.py
index eada14e7..b533dd8e 100644
--- a/Tests/svgLib/path/parser_test.py
+++ b/Tests/svgLib/path/parser_test.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, absolute_import, division
-
-from fontTools.misc.py23 import *
from fontTools.pens.recordingPen import RecordingPen
from fontTools.svgLib import parse_path
@@ -354,3 +351,86 @@ def test_arc_pen_with_arcTo():
]
assert pen.value == expected
+
+
+@pytest.mark.parametrize(
+ "path, expected",
+ [
+ (
+ "M1-2A3-4-1.0 01.5.7",
+ [
+ ("moveTo", ((1.0, -2.0),)),
+ ("arcTo", (3.0, -4.0, -1.0, False, True, (0.5, 0.7))),
+ ("endPath", ()),
+ ],
+ ),
+ (
+ "M21.58 7.19a2.51 2.51 0 10-1.77-1.77",
+ [
+ ("moveTo", ((21.58, 7.19),)),
+ ("arcTo", (2.51, 2.51, 0.0, True, False, (19.81, 5.42))),
+ ("endPath", ()),
+ ],
+ ),
+ (
+ "M22 12a25.87 25.87 0 00-.42-4.81",
+ [
+ ("moveTo", ((22.0, 12.0),)),
+ ("arcTo", (25.87, 25.87, 0.0, False, False, (21.58, 7.19))),
+ ("endPath", ()),
+ ],
+ ),
+ (
+ "M0,0 A1.2 1.2 0 012 15.8",
+ [
+ ("moveTo", ((0.0, 0.0),)),
+ ("arcTo", (1.2, 1.2, 0.0, False, True, (2.0, 15.8))),
+ ("endPath", ()),
+ ],
+ ),
+ (
+ "M12 7a5 5 0 105 5 5 5 0 00-5-5",
+ [
+
+ ("moveTo", ((12.0, 7.0),)),
+ ("arcTo", (5.0, 5.0, 0.0, True, False, (17.0, 12.0))),
+ ("arcTo", (5.0, 5.0, 0.0, False, False, (12.0, 7.0))),
+ ("endPath", ()),
+ ],
+ )
+ ],
+)
+def test_arc_flags_without_spaces(path, expected):
+ pen = ArcRecordingPen()
+ parse_path(path, pen)
+ assert pen.value == expected
+
+
+@pytest.mark.parametrize(
+ "path", ["A", "A0,0,0,0,0,0", "A 0 0 0 0 0 0 0 0 0 0 0 0 0"]
+)
+def test_invalid_arc_not_enough_args(path):
+ pen = ArcRecordingPen()
+ with pytest.raises(ValueError, match="Invalid arc command") as e:
+ parse_path(path, pen)
+
+ assert isinstance(e.value.__cause__, ValueError)
+ assert "Not enough arguments" in str(e.value.__cause__)
+
+
+def test_invalid_arc_argument_value():
+ pen = ArcRecordingPen()
+ with pytest.raises(ValueError, match="Invalid arc command") as e:
+ parse_path("M0,0 A0,0,0,2,0,0,0", pen)
+
+ cause = e.value.__cause__
+ assert isinstance(cause, ValueError)
+ assert "Invalid argument for 'large-arc-flag' parameter: '2'" in str(cause)
+
+ pen = ArcRecordingPen()
+ with pytest.raises(ValueError, match="Invalid arc command") as e:
+ parse_path("M0,0 A0,0,0,0,-2.0,0,0", pen)
+
+ cause = e.value.__cause__
+ assert isinstance(cause, ValueError)
+ assert "Invalid argument for 'sweep-flag' parameter: '-2.0'" in str(cause)
diff --git a/Tests/svgLib/path/path_test.py b/Tests/svgLib/path/path_test.py
index 09b94479..8ee334a0 100644
--- a/Tests/svgLib/path/path_test.py
+++ b/Tests/svgLib/path/path_test.py
@@ -1,6 +1,4 @@
-from __future__ import print_function, absolute_import, division
-
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes
from fontTools.pens.recordingPen import RecordingPen
from fontTools.svgLib import SVGPath
diff --git a/Tests/svgLib/path/shapes_test.py b/Tests/svgLib/path/shapes_test.py
index 99c70fdb..24e3dd2e 100644
--- a/Tests/svgLib/path/shapes_test.py
+++ b/Tests/svgLib/path/shapes_test.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, absolute_import, division
-
-from fontTools.misc.py23 import *
from fontTools.svgLib.path import shapes
from fontTools.misc import etree
import pytest
diff --git a/Tests/t1Lib/t1Lib_test.py b/Tests/t1Lib/t1Lib_test.py
index 153254d6..92b3e9e1 100644
--- a/Tests/t1Lib/t1Lib_test.py
+++ b/Tests/t1Lib/t1Lib_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
import unittest
import os
import sys
diff --git a/Tests/ttLib/data/woff2_overlap_offcurve_in.ttx b/Tests/ttLib/data/woff2_overlap_offcurve_in.ttx
new file mode 100644
index 00000000..a36dbf50
--- /dev/null
+++ b/Tests/ttLib/data/woff2_overlap_offcurve_in.ttx
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.18">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x9aec19bb"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000010"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Jan 28 15:17:57 2021"/>
+ <modified value="Thu Jan 28 15:52:10 2021"/>
+ <xMin value="178"/>
+ <yMin value="72"/>
+ <xMax value="586"/>
+ <yMax value="480"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="750"/>
+ <descent value="-250"/>
+ <lineGap value="100"/>
+ <advanceWidthMax value="639"/>
+ <minLeftSideBearing value="178"/>
+ <minRightSideBearing value="53"/>
+ <xMaxExtent value="586"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="2"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="2"/>
+ <maxPoints value="20"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="445"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000100"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 11000000"/>
+ <usFirstCharIndex value="65"/>
+ <usLastCharIndex value="65"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="100"/>
+ <usWinAscent value="750"/>
+ <usWinDescent value="250"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="250" lsb="0"/>
+ <mtx name="A" width="639" lsb="178"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ <TTGlyph name="A" xMin="178" yMin="72" xMax="586" yMax="480">
+ <contour>
+ <pt x="382" y="72" on="0" overlap="1"/>
+ <pt x="336" y="72" on="1"/>
+ <pt x="261" y="101" on="0"/>
+ <pt x="207" y="155" on="0"/>
+ <pt x="178" y="230" on="0"/>
+ <pt x="178" y="276" on="1"/>
+ <pt x="178" y="322" on="0"/>
+ <pt x="207" y="397" on="0"/>
+ <pt x="261" y="451" on="0"/>
+ <pt x="336" y="480" on="0"/>
+ <pt x="382" y="480" on="1"/>
+ <pt x="428" y="480" on="0"/>
+ <pt x="503" y="451" on="0"/>
+ <pt x="557" y="397" on="0"/>
+ <pt x="586" y="322" on="0"/>
+ <pt x="586" y="276" on="1"/>
+ <pt x="586" y="230" on="0"/>
+ <pt x="557" y="155" on="0"/>
+ <pt x="503" y="101" on="0"/>
+ <pt x="428" y="72" on="0"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Unnamed
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;Unnamed-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Unnamed Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ Unnamed-Regular
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ </extraNames>
+ </post>
+
+ <gasp>
+ <gaspRange rangeMaxPPEM="65535" rangeGaspBehavior="15"/>
+ </gasp>
+
+ <HVAR>
+ <Version value="0x00010000"/>
+ <VarStore Format="1">
+ <Format value="1"/>
+ <VarRegionList>
+ <!-- RegionAxisCount=1 -->
+ <!-- RegionCount=1 -->
+ <Region index="0">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.0"/>
+ <PeakCoord value="1.0"/>
+ <EndCoord value="1.0"/>
+ </VarRegionAxis>
+ </Region>
+ </VarRegionList>
+ <!-- VarDataCount=1 -->
+ <VarData index="0">
+ <!-- ItemCount=2 -->
+ <NumShorts value="0"/>
+ <!-- VarRegionCount=0 -->
+ <Item index="0" value="[]"/>
+ <Item index="1" value="[]"/>
+ </VarData>
+ </VarStore>
+ </HVAR>
+
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=1 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=0 -->
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
+ <fvar>
+
+ <!-- Weight -->
+ <Axis>
+ <AxisTag>wght</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>400.0</MinValue>
+ <DefaultValue>400.0</DefaultValue>
+ <MaxValue>700.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+ </fvar>
+
+ <gvar>
+ <version value="1"/>
+ <reserved value="0"/>
+ <glyphVariations glyph="A">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="-64" y="-44"/>
+ <delta pt="5" x="-138" y="30"/>
+ <delta pt="7" x="-127" y="73"/>
+ <delta pt="8" x="-108" y="92"/>
+ <delta pt="10" x="-64" y="103"/>
+ <delta pt="12" x="-21" y="92"/>
+ <delta pt="13" x="-2" y="73"/>
+ <delta pt="16" x="9" y="13"/>
+ <delta pt="17" x="-2" y="-14"/>
+ <delta pt="18" x="-21" y="-33"/>
+ </tuple>
+ </glyphVariations>
+ </gvar>
+
+</ttFont>
diff --git a/Tests/ttLib/sfnt_test.py b/Tests/ttLib/sfnt_test.py
index 2119351d..9f817444 100644
--- a/Tests/ttLib/sfnt_test.py
+++ b/Tests/ttLib/sfnt_test.py
@@ -1,8 +1,59 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-from fontTools.ttLib.sfnt import calcChecksum
+import io
+import copy
+import pickle
+from fontTools.ttLib.sfnt import calcChecksum, SFNTReader
+import pytest
def test_calcChecksum():
assert calcChecksum(b"abcd") == 1633837924
assert calcChecksum(b"abcdxyz") == 3655064932
+
+
+EMPTY_SFNT = b"\x00\x01\x00\x00" + b"\x00" * 8
+
+
+def pickle_unpickle(obj):
+ return pickle.loads(pickle.dumps(obj))
+
+
+class SFNTReaderTest:
+ @pytest.mark.parametrize("deepcopy", [copy.deepcopy, pickle_unpickle])
+ def test_pickle_protocol_FileIO(self, deepcopy, tmp_path):
+ fontfile = tmp_path / "test.ttf"
+ fontfile.write_bytes(EMPTY_SFNT)
+ reader = SFNTReader(fontfile.open("rb"))
+
+ reader2 = deepcopy(reader)
+
+ assert reader2 is not reader
+ assert reader2.file is not reader.file
+
+ assert isinstance(reader2.file, io.BufferedReader)
+ assert isinstance(reader2.file.raw, io.FileIO)
+ assert reader2.file.name == reader.file.name
+ assert reader2.file.tell() == reader.file.tell()
+
+ for k, v in reader.__dict__.items():
+ if k == "file":
+ continue
+ assert getattr(reader2, k) == v
+
+ @pytest.mark.parametrize("deepcopy", [copy.deepcopy, pickle_unpickle])
+ def test_pickle_protocol_BytesIO(self, deepcopy, tmp_path):
+ buf = io.BytesIO(EMPTY_SFNT)
+ reader = SFNTReader(buf)
+
+ reader2 = deepcopy(reader)
+
+ assert reader2 is not reader
+ assert reader2.file is not reader.file
+
+ assert isinstance(reader2.file, io.BytesIO)
+ assert reader2.file.tell() == reader.file.tell()
+ assert reader2.file.getvalue() == reader.file.getvalue()
+
+ for k, v in reader.__dict__.items():
+ if k == "file":
+ continue
+ assert getattr(reader2, k) == v
diff --git a/Tests/ttLib/tables/C_B_L_C_test.py b/Tests/ttLib/tables/C_B_L_C_test.py
new file mode 100644
index 00000000..fed25f99
--- /dev/null
+++ b/Tests/ttLib/tables/C_B_L_C_test.py
@@ -0,0 +1,80 @@
+import base64
+import io
+import os
+
+from fontTools.misc.testTools import getXML
+from fontTools.ttLib import TTFont
+
+
+DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data")
+
+# This is a subset from NotoColorEmoji.ttf which contains an IndexTable format=3
+INDEX_FORMAT_3_TTX = os.path.join(DATA_DIR, "NotoColorEmoji.subset.index_format_3.ttx")
+# The CLBC table was compiled with Harfbuzz' hb-subset and contains the correct padding
+CBLC_INDEX_FORMAT_3 = base64.b64decode(
+ "AAMAAAAAAAEAAAA4AAAALAAAAAIAAAAAZeWIAAAAAAAAAAAAZeWIAAAAAAAAAAAAAAEAA"
+ "21tIAEAAQACAAAAEAADAAMAAAAgAAMAEQAAAAQAAAOmEQ0AAAADABEAABERAAAIUg=="
+)
+
+
+def test_compile_decompile_index_table_format_3():
+ font = TTFont()
+ font.importXML(INDEX_FORMAT_3_TTX)
+ buf = io.BytesIO()
+ font.save(buf)
+ buf.seek(0)
+ font = TTFont(buf)
+
+ assert font.reader["CBLC"] == CBLC_INDEX_FORMAT_3
+
+ assert getXML(font["CBLC"].toXML, font) == [
+ '<header version="3.0"/>',
+ '<strike index="0">',
+ " <bitmapSizeTable>",
+ ' <sbitLineMetrics direction="hori">',
+ ' <ascender value="101"/>',
+ ' <descender value="-27"/>',
+ ' <widthMax value="136"/>',
+ ' <caretSlopeNumerator value="0"/>',
+ ' <caretSlopeDenominator value="0"/>',
+ ' <caretOffset value="0"/>',
+ ' <minOriginSB value="0"/>',
+ ' <minAdvanceSB value="0"/>',
+ ' <maxBeforeBL value="0"/>',
+ ' <minAfterBL value="0"/>',
+ ' <pad1 value="0"/>',
+ ' <pad2 value="0"/>',
+ " </sbitLineMetrics>",
+ ' <sbitLineMetrics direction="vert">',
+ ' <ascender value="101"/>',
+ ' <descender value="-27"/>',
+ ' <widthMax value="136"/>',
+ ' <caretSlopeNumerator value="0"/>',
+ ' <caretSlopeDenominator value="0"/>',
+ ' <caretOffset value="0"/>',
+ ' <minOriginSB value="0"/>',
+ ' <minAdvanceSB value="0"/>',
+ ' <maxBeforeBL value="0"/>',
+ ' <minAfterBL value="0"/>',
+ ' <pad1 value="0"/>',
+ ' <pad2 value="0"/>',
+ " </sbitLineMetrics>",
+ ' <colorRef value="0"/>',
+ ' <startGlyphIndex value="1"/>',
+ ' <endGlyphIndex value="3"/>',
+ ' <ppemX value="109"/>',
+ ' <ppemY value="109"/>',
+ ' <bitDepth value="32"/>',
+ ' <flags value="1"/>',
+ " </bitmapSizeTable>",
+ " <!-- GlyphIds are written but not read. The firstGlyphIndex and",
+ " lastGlyphIndex values will be recalculated by the compiler. -->",
+ ' <eblc_index_sub_table_3 imageFormat="17" firstGlyphIndex="1" lastGlyphIndex="2">',
+ ' <glyphLoc id="1" name="eight"/>',
+ ' <glyphLoc id="2" name="registered"/>',
+ " </eblc_index_sub_table_3>",
+ ' <eblc_index_sub_table_3 imageFormat="17" firstGlyphIndex="3" lastGlyphIndex="3">',
+ ' <glyphLoc id="3" name="uni2049"/>',
+ " </eblc_index_sub_table_3>",
+ "</strike>",
+ ]
diff --git a/Tests/ttLib/tables/C_F_F__2_test.py b/Tests/ttLib/tables/C_F_F__2_test.py
index 191f60bc..10f9b2fb 100644
--- a/Tests/ttLib/tables/C_F_F__2_test.py
+++ b/Tests/ttLib/tables/C_F_F__2_test.py
@@ -1,9 +1,7 @@
"""cff2Lib_test.py -- unit test for Adobe CFF fonts."""
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
-from fontTools.ttLib import TTFont, newTable
+from fontTools.ttLib import TTFont
+from io import StringIO
import re
import os
import unittest
@@ -41,7 +39,7 @@ class CFFTableTest(unittest.TestCase):
font = TTFont(file=CFF_BIN)
cffTable = font['CFF2']
cffData = cffTable.compile(font)
- out = UnicodeIO()
+ out = StringIO()
font.saveXML(out)
cff2XML = out.getvalue()
cff2XML = strip_VariableItems(cff2XML)
diff --git a/Tests/ttLib/tables/C_F_F_test.py b/Tests/ttLib/tables/C_F_F_test.py
index 63f767cc..cb8d8c55 100644
--- a/Tests/ttLib/tables/C_F_F_test.py
+++ b/Tests/ttLib/tables/C_F_F_test.py
@@ -1,9 +1,7 @@
"""cffLib_test.py -- unit test for Adobe CFF fonts."""
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont, newTable
+from io import StringIO
import re
import os
import unittest
@@ -33,7 +31,7 @@ class CFFTableTest(unittest.TestCase):
font = TTFont(sfntVersion='OTTO')
cffTable = font['CFF '] = newTable('CFF ')
cffTable.decompile(self.cffData, font)
- out = UnicodeIO()
+ out = StringIO()
font.saveXML(out)
cffXML = strip_ttLibVersion(out.getvalue()).splitlines()
self.assertEqual(cffXML, self.cffXML)
diff --git a/Tests/ttLib/tables/C_O_L_R_test.py b/Tests/ttLib/tables/C_O_L_R_test.py
new file mode 100644
index 00000000..4855f58f
--- /dev/null
+++ b/Tests/ttLib/tables/C_O_L_R_test.py
@@ -0,0 +1,512 @@
+from fontTools import ttLib
+from fontTools.misc.testTools import getXML, parseXML
+from fontTools.ttLib.tables.C_O_L_R_ import table_C_O_L_R_
+
+import binascii
+import pytest
+
+
+COLR_V0_SAMPLE = (
+ (b"\x00\x00", "Version (0)"),
+ (b"\x00\x01", "BaseGlyphRecordCount (1)"),
+ (
+ b"\x00\x00\x00\x0e",
+ "Offset to BaseGlyphRecordArray from beginning of table (14)",
+ ),
+ (b"\x00\x00\x00\x14", "Offset to LayerRecordArray from beginning of table (20)"),
+ (b"\x00\x03", "LayerRecordCount (3)"),
+ (b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"),
+ (b"\x00\x00", "BaseGlyphRecord[0].FirstLayerIndex (0)"),
+ (b"\x00\x03", "BaseGlyphRecord[0].NumLayers (3)"),
+ (b"\x00\x07", "LayerRecord[0].LayerGlyph (7)"),
+ (b"\x00\x00", "LayerRecord[0].PaletteIndex (0)"),
+ (b"\x00\x08", "LayerRecord[1].LayerGlyph (8)"),
+ (b"\x00\x01", "LayerRecord[1].PaletteIndex (1)"),
+ (b"\x00\t", "LayerRecord[2].LayerGlyph (9)"),
+ (b"\x00\x02", "LayerRecord[3].PaletteIndex (2)"),
+)
+
+COLR_V0_DATA = b"".join(t[0] for t in COLR_V0_SAMPLE)
+
+
+COLR_V0_XML = [
+ '<version value="0"/>',
+ '<ColorGlyph name="glyph00006">',
+ ' <layer colorID="0" name="glyph00007"/>',
+ ' <layer colorID="1" name="glyph00008"/>',
+ ' <layer colorID="2" name="glyph00009"/>',
+ "</ColorGlyph>",
+]
+
+
+def dump(table, ttFont=None):
+ print("\n".join(getXML(table.toXML, ttFont)))
+
+
+def diff_binary_fragments(font_bytes, expected_fragments):
+ pos = 0
+ prev_desc = ""
+ errors = 0
+ for expected_bytes, description in expected_fragments:
+ actual_bytes = font_bytes[pos : pos + len(expected_bytes)]
+ if actual_bytes != expected_bytes:
+ print(f'{description} (previous "{prev_desc}", actual_bytes: {"".join("%02x" % v for v in actual_bytes)} bytes: {str(font_bytes[pos:pos+16])}')
+ errors += 1
+ pos += len(expected_bytes)
+ prev_desc = description
+ assert errors == 0
+ assert pos == len(
+ font_bytes
+ ), f"Leftover font bytes, used {pos} of {len(font_bytes)}"
+
+
+@pytest.fixture
+def font():
+ font = ttLib.TTFont()
+ font.setGlyphOrder(["glyph%05d" % i for i in range(30)])
+ return font
+
+
+class COLR_V0_Test(object):
+ def test_decompile_and_compile(self, font):
+ colr = table_C_O_L_R_()
+ colr.decompile(COLR_V0_DATA, font)
+ diff_binary_fragments(colr.compile(font), COLR_V0_SAMPLE)
+
+ def test_decompile_and_dump_xml(self, font):
+ colr = table_C_O_L_R_()
+ colr.decompile(COLR_V0_DATA, font)
+
+ dump(colr, font)
+ assert getXML(colr.toXML, font) == COLR_V0_XML
+
+ def test_load_from_xml_and_compile(self, font):
+ colr = table_C_O_L_R_()
+ for name, attrs, content in parseXML(COLR_V0_XML):
+ colr.fromXML(name, attrs, content, font)
+
+ diff_binary_fragments(colr.compile(font), COLR_V0_SAMPLE)
+
+ def test_round_trip_xml(self, font):
+ colr = table_C_O_L_R_()
+ for name, attrs, content in parseXML(COLR_V0_XML):
+ colr.fromXML(name, attrs, content, font)
+ compiled = colr.compile(font)
+
+ colr = table_C_O_L_R_()
+ colr.decompile(compiled, font)
+ assert getXML(colr.toXML, font) == COLR_V0_XML
+
+
+COLR_V1_SAMPLE = (
+ (b"\x00\x01", "Version (1)"),
+ (b"\x00\x01", "BaseGlyphRecordCount (1)"),
+ (
+ b"\x00\x00\x00\x1a",
+ "Offset to BaseGlyphRecordArray from beginning of table (26)",
+ ),
+ (b"\x00\x00\x00 ", "Offset to LayerRecordArray from beginning of table (32)"),
+ (b"\x00\x03", "LayerRecordCount (3)"),
+ (b"\x00\x00\x00,", "Offset to BaseGlyphV1List from beginning of table (44)"),
+ (b"\x00\x00\x00\xac", "Offset to LayerV1List from beginning of table (172)"),
+ (b"\x00\x00\x00\x00", "Offset to VarStore (NULL)"),
+ (b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"),
+ (b"\x00\x00", "BaseGlyphRecord[0].FirstLayerIndex (0)"),
+ (b"\x00\x03", "BaseGlyphRecord[0].NumLayers (3)"),
+ (b"\x00\x07", "LayerRecord[0].LayerGlyph (7)"),
+ (b"\x00\x00", "LayerRecord[0].PaletteIndex (0)"),
+ (b"\x00\x08", "LayerRecord[1].LayerGlyph (8)"),
+ (b"\x00\x01", "LayerRecord[1].PaletteIndex (1)"),
+ (b"\x00\t", "LayerRecord[2].LayerGlyph (9)"),
+ (b"\x00\x02", "LayerRecord[2].PaletteIndex (2)"),
+ # BaseGlyphV1List
+ (b"\x00\x00\x00\x03", "BaseGlyphV1List.BaseGlyphCount (3)"),
+ (b"\x00\n", "BaseGlyphV1List.BaseGlyphV1Record[0].BaseGlyph (10)"),
+ (
+ b"\x00\x00\x00\x16",
+ "Offset to Paint table from beginning of BaseGlyphV1List (22)",
+ ),
+ (b"\x00\x0e", "BaseGlyphV1List.BaseGlyphV1Record[1].BaseGlyph (14)"),
+ (
+ b"\x00\x00\x00\x1c",
+ "Offset to Paint table from beginning of BaseGlyphV1List (28)",
+ ),
+ (b"\x00\x0f", "BaseGlyphV1List.BaseGlyphV1Record[2].BaseGlyph (15)"),
+ (
+ b"\x00\x00\x00\x5b",
+ "Offset to Paint table from beginning of BaseGlyphV1List (91)",
+ ),
+ # BaseGlyphV1Record[0]
+ (b"\x01", "BaseGlyphV1Record[0].Paint.Format (1)"),
+ (b"\x04", "BaseGlyphV1Record[0].Paint.NumLayers (4)"),
+ (b"\x00\x00\x00\x00", "BaseGlyphV1Record[0].Paint.FirstLayerIndex (0)"),
+ # BaseGlyphV1Record[1]
+ (b"\x14", "BaseGlyphV1Record[1].Paint.Format (20)"),
+ (b"\x00\x00<", "Offset to SourcePaint from beginning of PaintComposite (60)"),
+ (b"\x03", "BaseGlyphV1Record[1].Paint.CompositeMode [SRC_OVER] (3)"),
+ (b"\x00\x00\x08", "Offset to BackdropPaint from beginning of PaintComposite (8)"),
+ (b"\x0d", "BaseGlyphV1Record[1].Paint.BackdropPaint.Format (13)"),
+ (b"\x00\x00\x34", "Offset to Paint from beginning of PaintVarTransform (52)"),
+ (b"\x00\x01\x00\x00\x00\x00\x00\x00", "Affine2x3.xx.value (1.0)"),
+ (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.xy.value (0.0)"),
+ (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.yx.value (0.0)"),
+ (b"\x00\x01\x00\x00\x00\x00\x00\x00", "Affine2x3.yy.value (1.0)"),
+ (b"\x01\x2c\x00\x00\x00\x00\x00\x00", "Affine2x3.dx.value (300.0)"),
+ (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.dy.value (0.0)"),
+ (b"\x0b", "BaseGlyphV1Record[1].Paint.SourcePaint.Format (11)"),
+ (b"\x00\n", "BaseGlyphV1Record[1].Paint.SourcePaint.Glyph (10)"),
+ # BaseGlyphV1Record[2]
+ (b"\x0a", "BaseGlyphV1Record[2].Paint.Format (10)"),
+ (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
+ (b"\x00\x0b", "BaseGlyphV1Record[2].Paint.Glyph (11)"),
+ (b"\x08", "BaseGlyphV1Record[2].Paint.Paint.Format (8)"),
+ (b"\x00\x00\x10", "Offset to ColorLine from beginning of PaintSweepGradient (16)"),
+ (b"\x01\x03", "centerX (259)"),
+ (b"\x01\x2c", "centerY (300)"),
+ (b"\x00\x2d\x00\x00", "startAngle (45.0)"),
+ (b"\x00\x87\x00\x00", "endAngle (135.0)"),
+ (b"\x00", "ColorLine.Extend (0; pad)"),
+ (b"\x00\x02", "ColorLine.StopCount (2)"),
+ (b"\x00\x00", "ColorLine.ColorStop[0].StopOffset (0.0)"),
+ (b"\x00\x03", "ColorLine.ColorStop[0].Color.PaletteIndex (3)"),
+ (b"@\x00", "ColorLine.ColorStop[0].Color.Alpha (1.0)"),
+ (b"@\x00", "ColorLine.ColorStop[1].StopOffset (1.0)"),
+ (b"\x00\x05", "ColorLine.ColorStop[1].Color.PaletteIndex (5)"),
+ (b"@\x00", "ColorLine.ColorStop[1].Color.Alpha (1.0)"),
+ # LayerV1List
+ (b"\x00\x00\x00\x04", "LayerV1List.LayerCount (4)"),
+ (
+ b"\x00\x00\x00\x14",
+ "First Offset to Paint table from beginning of LayerV1List (20)",
+ ),
+ (
+ b"\x00\x00\x00\x23",
+ "Second Offset to Paint table from beginning of LayerV1List (35)",
+ ),
+ (
+ b"\x00\x00\x00\x4e",
+ "Third Offset to Paint table from beginning of LayerV1List (78)",
+ ),
+ (
+ b"\x00\x00\x00\xb7",
+ "Fourth Offset to Paint table from beginning of LayerV1List (183)",
+ ),
+ # PaintGlyph glyph00011
+ (b"\x0a", "LayerV1List.Paint[0].Format (10)"),
+ (b"\x00\x00\x06", "Offset24 to Paint subtable from beginning of PaintGlyph (6)"),
+ (b"\x00\x0b", "LayerV1List.Paint[0].Glyph (glyph00011)"),
+ # PaintVarSolid
+ (b"\x03", "LayerV1List.Paint[0].Paint.Format (3)"),
+ (b"\x00\x02", "Paint.Color.PaletteIndex (2)"),
+ (b" \x00", "Paint.Color.Alpha.value (0.5)"),
+ (b"\x00\x00\x00\x00", "Paint.Color.Alpha.varIdx (0)"),
+ # PaintGlyph glyph00012
+ (b"\x0a", "LayerV1List.Paint[1].Format (10)"),
+ (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
+ (b"\x00\x0c", "LayerV1List.Paint[1].Glyph (glyph00012)"),
+ (b"\x04", "LayerV1List.Paint[1].Paint.Format (4)"),
+ (b"\x00\x00\x10", "Offset to ColorLine from beginning of PaintLinearGradient (16)"),
+ (b"\x00\x01", "Paint.x0 (1)"),
+ (b"\x00\x02", "Paint.y0 (2)"),
+ (b"\xff\xfd", "Paint.x1 (-3)"),
+ (b"\xff\xfc", "Paint.y1 (-4)"),
+ (b"\x00\x05", "Paint.x2 (5)"),
+ (b"\x00\x06", "Paint.y2 (6)"),
+ (b"\x01", "ColorLine.Extend (1; repeat)"),
+ (b"\x00\x03", "ColorLine.StopCount (3)"),
+ (b"\x00\x00", "ColorLine.ColorStop[0].StopOffset (0.0)"),
+ (b"\x00\x03", "ColorLine.ColorStop[0].Color.PaletteIndex (3)"),
+ (b"@\x00", "ColorLine.ColorStop[0].Color.Alpha (1.0)"),
+ (b" \x00", "ColorLine.ColorStop[1].StopOffset (0.5)"),
+ (b"\x00\x04", "ColorLine.ColorStop[1].Color.PaletteIndex (4)"),
+ (b"@\x00", "ColorLine.ColorStop[1].Color.Alpha (1.0)"),
+ (b"@\x00", "ColorLine.ColorStop[2].StopOffset (1.0)"),
+ (b"\x00\x05", "ColorLine.ColorStop[2].Color.PaletteIndex (5)"),
+ (b"@\x00", "ColorLine.ColorStop[2].Color.Alpha (1.0)"),
+ # PaintGlyph glyph00013
+ (b"\x0a", "LayerV1List.Paint[2].Format (10)"),
+ (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
+ (b"\x00\r", "LayerV1List.Paint[2].Glyph (13)"),
+ (b"\x0c", "LayerV1List.Paint[2].Paint.Format (12)"),
+ (b"\x00\x00\x1c", "Offset to Paint subtable from beginning of PaintTransform (28)"),
+ (b"\xff\xf3\x00\x00", "Affine2x3.xx (-13)"),
+ (b"\x00\x0e\x00\x00", "Affine2x3.xy (14)"),
+ (b"\x00\x0f\x00\x00", "Affine2x3.yx (15)"),
+ (b"\xff\xef\x00\x00", "Affine2x3.yy (-17)"),
+ (b"\x00\x12\x00\x00", "Affine2x3.yy (18)"),
+ (b"\x00\x13\x00\x00", "Affine2x3.yy (19)"),
+ (b"\x07", "LayerV1List.Paint[2].Paint.Paint.Format (7)"),
+ (b"\x00\x00(", "Offset to ColorLine from beginning of PaintVarRadialGradient (40)"),
+ (b"\x00\x07\x00\x00\x00\x00", "Paint.x0.value (7)"),
+ (b"\x00\x08\x00\x00\x00\x00", "Paint.y0.value (8)"),
+ (b"\x00\t\x00\x00\x00\x00", "Paint.r0.value (9)"),
+ (b"\x00\n\x00\x00\x00\x00", "Paint.x1.value (10)"),
+ (b"\x00\x0b\x00\x00\x00\x00", "Paint.y1.value (11)"),
+ (b"\x00\x0c\x00\x00\x00\x00", "Paint.r1.value (12)"),
+ (b"\x00", "ColorLine.Extend (0; pad)"),
+ (b"\x00\x02", "ColorLine.StopCount (2)"),
+ (b"\x00\x00\x00\x00\x00\x00", "ColorLine.ColorStop[0].StopOffset.value (0.0)"),
+ (b"\x00\x06", "ColorLine.ColorStop[0].Color.PaletteIndex (6)"),
+ (b"@\x00\x00\x00\x00\x00", "ColorLine.ColorStop[0].Color.Alpha.value (1.0)"),
+ (b"@\x00\x00\x00\x00\x00", "ColorLine.ColorStop[1].StopOffset.value (1.0)"),
+ (b"\x00\x07", "ColorLine.ColorStop[1].Color.PaletteIndex (7)"),
+ (b"\x19\x9a\x00\x00\x00\x00", "ColorLine.ColorStop[1].Color.Alpha.value (0.4)"),
+ # PaintTranslate
+ (b"\x0e", "LayerV1List.Paint[3].Format (14)"),
+ (b"\x00\x00\x0c", "Offset to Paint subtable from beginning of PaintTranslate (12)"),
+ (b"\x01\x01\x00\x00", "dx (257)"),
+ (b"\x01\x02\x00\x00", "dy (258)"),
+ # PaintRotate
+ (b"\x10", "LayerV1List.Paint[3].Paint.Format (16)"),
+ (b"\x00\x00\x10", "Offset to Paint subtable from beginning of PaintRotate (16)"),
+ (b"\x00\x2d\x00\x00", "angle (45)"),
+ (b"\x00\xff\x00\x00", "centerX (255)"),
+ (b"\x01\x00\x00\x00", "centerY (256)"),
+ # PaintSkew
+ (b"\x12", "LayerV1List.Paint[3].Paint.Paint.Format (18)"),
+ (b"\x00\x00\x14", "Offset to Paint subtable from beginning of PaintSkew (20)"),
+ (b"\xff\xf5\x00\x00", "xSkewAngle (-11)"),
+ (b"\x00\x05\x00\x00", "ySkewAngle (5)"),
+ (b"\x00\xfd\x00\x00", "centerX.value (253)"),
+ (b"\x00\xfe\x00\x00", "centerY.value (254)"),
+ # PaintGlyph
+ (b"\x0a", "LayerV1List.Paint[3].Paint.Paint.Paint.Format (10)"),
+ (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
+ (b"\x00\x0b", "LayerV1List.Paint[2].Glyph (11)"),
+ # PaintSolid
+ (b"\x02", "LayerV1List.Paint[0].Paint.Paint.Paint.Paint.Format (2)"),
+ (b"\x00\x02", "Paint.Color.PaletteIndex (2)"),
+ (b" \x00", "Paint.Color.Alpha (0.5)"),
+)
+
+COLR_V1_DATA = b"".join(t[0] for t in COLR_V1_SAMPLE)
+
+COLR_V1_XML = [
+ '<Version value="1"/>',
+ "<!-- BaseGlyphRecordCount=1 -->",
+ "<BaseGlyphRecordArray>",
+ ' <BaseGlyphRecord index="0">',
+ ' <BaseGlyph value="glyph00006"/>',
+ ' <FirstLayerIndex value="0"/>',
+ ' <NumLayers value="3"/>',
+ " </BaseGlyphRecord>",
+ "</BaseGlyphRecordArray>",
+ "<LayerRecordArray>",
+ ' <LayerRecord index="0">',
+ ' <LayerGlyph value="glyph00007"/>',
+ ' <PaletteIndex value="0"/>',
+ " </LayerRecord>",
+ ' <LayerRecord index="1">',
+ ' <LayerGlyph value="glyph00008"/>',
+ ' <PaletteIndex value="1"/>',
+ " </LayerRecord>",
+ ' <LayerRecord index="2">',
+ ' <LayerGlyph value="glyph00009"/>',
+ ' <PaletteIndex value="2"/>',
+ " </LayerRecord>",
+ "</LayerRecordArray>",
+ "<!-- LayerRecordCount=3 -->",
+ "<BaseGlyphV1List>",
+ " <!-- BaseGlyphCount=3 -->",
+ ' <BaseGlyphV1Record index="0">',
+ ' <BaseGlyph value="glyph00010"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="4"/>',
+ ' <FirstLayerIndex value="0"/>',
+ " </Paint>",
+ " </BaseGlyphV1Record>",
+ ' <BaseGlyphV1Record index="1">',
+ ' <BaseGlyph value="glyph00014"/>',
+ ' <Paint Format="20"><!-- PaintComposite -->',
+ ' <SourcePaint Format="11"><!-- PaintColrGlyph -->',
+ ' <Glyph value="glyph00010"/>',
+ " </SourcePaint>",
+ ' <CompositeMode value="src_over"/>',
+ ' <BackdropPaint Format="13"><!-- PaintVarTransform -->',
+ ' <Paint Format="11"><!-- PaintColrGlyph -->',
+ ' <Glyph value="glyph00010"/>',
+ " </Paint>",
+ " <Transform>",
+ ' <xx value="1.0"/>',
+ ' <yx value="0.0"/>',
+ ' <xy value="0.0"/>',
+ ' <yy value="1.0"/>',
+ ' <dx value="300.0"/>',
+ ' <dy value="0.0"/>',
+ " </Transform>",
+ " </BackdropPaint>",
+ " </Paint>",
+ " </BaseGlyphV1Record>",
+ ' <BaseGlyphV1Record index="2">',
+ ' <BaseGlyph value="glyph00015"/>',
+ ' <Paint Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="8"><!-- PaintSweepGradient -->',
+ " <ColorLine>",
+ ' <Extend value="pad"/>',
+ " <!-- StopCount=2 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.0"/>',
+ " <Color>",
+ ' <PaletteIndex value="3"/>',
+ ' <Alpha value="1.0"/>',
+ " </Color>",
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="1.0"/>',
+ " <Color>",
+ ' <PaletteIndex value="5"/>',
+ ' <Alpha value="1.0"/>',
+ " </Color>",
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <centerX value="259"/>',
+ ' <centerY value="300"/>',
+ ' <startAngle value="45.0"/>',
+ ' <endAngle value="135.0"/>',
+ " </Paint>",
+ ' <Glyph value="glyph00011"/>',
+ " </Paint>",
+ " </BaseGlyphV1Record>",
+ "</BaseGlyphV1List>",
+ "<LayerV1List>",
+ " <!-- LayerCount=4 -->",
+ ' <Paint index="0" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="3"><!-- PaintVarSolid -->',
+ " <Color>",
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="0.5"/>',
+ " </Color>",
+ " </Paint>",
+ ' <Glyph value="glyph00011"/>',
+ " </Paint>",
+ ' <Paint index="1" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="4"><!-- PaintLinearGradient -->',
+ " <ColorLine>",
+ ' <Extend value="repeat"/>',
+ " <!-- StopCount=3 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.0"/>',
+ " <Color>",
+ ' <PaletteIndex value="3"/>',
+ ' <Alpha value="1.0"/>',
+ " </Color>",
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="0.5"/>',
+ " <Color>",
+ ' <PaletteIndex value="4"/>',
+ ' <Alpha value="1.0"/>',
+ " </Color>",
+ " </ColorStop>",
+ ' <ColorStop index="2">',
+ ' <StopOffset value="1.0"/>',
+ " <Color>",
+ ' <PaletteIndex value="5"/>',
+ ' <Alpha value="1.0"/>',
+ " </Color>",
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <x0 value="1"/>',
+ ' <y0 value="2"/>',
+ ' <x1 value="-3"/>',
+ ' <y1 value="-4"/>',
+ ' <x2 value="5"/>',
+ ' <y2 value="6"/>',
+ " </Paint>",
+ ' <Glyph value="glyph00012"/>',
+ " </Paint>",
+ ' <Paint index="2" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="12"><!-- PaintTransform -->',
+ ' <Paint Format="7"><!-- PaintVarRadialGradient -->',
+ " <ColorLine>",
+ ' <Extend value="pad"/>',
+ " <!-- StopCount=2 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.0"/>',
+ " <Color>",
+ ' <PaletteIndex value="6"/>',
+ ' <Alpha value="1.0"/>',
+ " </Color>",
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="1.0"/>',
+ " <Color>",
+ ' <PaletteIndex value="7"/>',
+ ' <Alpha value="0.4"/>',
+ " </Color>",
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <x0 value="7"/>',
+ ' <y0 value="8"/>',
+ ' <r0 value="9"/>',
+ ' <x1 value="10"/>',
+ ' <y1 value="11"/>',
+ ' <r1 value="12"/>',
+ " </Paint>",
+ " <Transform>",
+ ' <xx value="-13.0"/>',
+ ' <yx value="14.0"/>',
+ ' <xy value="15.0"/>',
+ ' <yy value="-17.0"/>',
+ ' <dx value="18.0"/>',
+ ' <dy value="19.0"/>',
+ " </Transform>",
+ " </Paint>",
+ ' <Glyph value="glyph00013"/>',
+ " </Paint>",
+ ' <Paint index="3" Format="14"><!-- PaintTranslate -->',
+ ' <Paint Format="16"><!-- PaintRotate -->',
+ ' <Paint Format="18"><!-- PaintSkew -->',
+ ' <Paint Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ " <Color>",
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="0.5"/>',
+ " </Color>",
+ " </Paint>",
+ ' <Glyph value="glyph00011"/>',
+ " </Paint>",
+ ' <xSkewAngle value="-11.0"/>',
+ ' <ySkewAngle value="5.0"/>',
+ ' <centerX value="253.0"/>',
+ ' <centerY value="254.0"/>',
+ " </Paint>",
+ ' <angle value="45.0"/>',
+ ' <centerX value="255.0"/>',
+ ' <centerY value="256.0"/>',
+ " </Paint>",
+ ' <dx value="257.0"/>',
+ ' <dy value="258.0"/>',
+ " </Paint>",
+ "</LayerV1List>",
+]
+
+
+class COLR_V1_Test(object):
+ def test_decompile_and_compile(self, font):
+ colr = table_C_O_L_R_()
+ colr.decompile(COLR_V1_DATA, font)
+ diff_binary_fragments(colr.compile(font), COLR_V1_SAMPLE)
+
+ def test_decompile_and_dump_xml(self, font):
+ colr = table_C_O_L_R_()
+ colr.decompile(COLR_V1_DATA, font)
+
+ dump(colr, font)
+ assert getXML(colr.toXML, font) == COLR_V1_XML
+
+ def test_load_from_xml_and_compile(self, font):
+ colr = table_C_O_L_R_()
+ for name, attrs, content in parseXML(COLR_V1_XML):
+ colr.fromXML(name, attrs, content, font)
+ diff_binary_fragments(colr.compile(font), COLR_V1_SAMPLE)
+
+ def test_round_trip_xml(self, font):
+ colr = table_C_O_L_R_()
+ for name, attrs, content in parseXML(COLR_V1_XML):
+ colr.fromXML(name, attrs, content, font)
+ compiled = colr.compile(font)
+
+ colr = table_C_O_L_R_()
+ colr.decompile(compiled, font)
+ assert getXML(colr.toXML, font) == COLR_V1_XML
diff --git a/Tests/ttLib/tables/C_P_A_L_test.py b/Tests/ttLib/tables/C_P_A_L_test.py
index bacd4a87..10c8ea0e 100644
--- a/Tests/ttLib/tables/C_P_A_L_test.py
+++ b/Tests/ttLib/tables/C_P_A_L_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import getXML, parseXML
from fontTools.misc.textTools import deHexStr
from fontTools.ttLib import getTableModule, newTable
@@ -67,9 +65,6 @@ class CPALTest(unittest.TestCase):
self.assertEqual(cpal.numPaletteEntries, 2)
self.assertEqual(repr(cpal.palettes),
'[[#000000FF, #66CCFFFF], [#000000FF, #800000FF]]')
- self.assertEqual(cpal.paletteLabels, [0, 0])
- self.assertEqual(cpal.paletteTypes, [0, 0])
- self.assertEqual(cpal.paletteEntryLabels, [0, 0])
def test_decompile_v0_sharingColors(self):
cpal = newTable('CPAL')
@@ -81,9 +76,6 @@ class CPALTest(unittest.TestCase):
'[#223344FF, #99887711, #55555555]',
'[#223344FF, #99887711, #FFFFFFFF]',
'[#223344FF, #99887711, #55555555]'])
- self.assertEqual(cpal.paletteLabels, [0, 0, 0, 0])
- self.assertEqual(cpal.paletteTypes, [0, 0, 0, 0])
- self.assertEqual(cpal.paletteEntryLabels, [0, 0, 0])
def test_decompile_v1_noLabelsNoTypes(self):
cpal = newTable('CPAL')
@@ -93,9 +85,10 @@ class CPALTest(unittest.TestCase):
self.assertEqual([repr(p) for p in cpal.palettes], [
'[#CAFECAFE, #22110033, #66554477]', # RGBA
'[#59413127, #42424242, #13330037]'])
- self.assertEqual(cpal.paletteLabels, [0, 0])
+ self.assertEqual(cpal.paletteLabels, [cpal.NO_NAME_ID] * len(cpal.palettes))
self.assertEqual(cpal.paletteTypes, [0, 0])
- self.assertEqual(cpal.paletteEntryLabels, [0, 0, 0])
+ self.assertEqual(cpal.paletteEntryLabels,
+ [cpal.NO_NAME_ID] * cpal.numPaletteEntries)
def test_decompile_v1(self):
cpal = newTable('CPAL')
@@ -195,9 +188,6 @@ class CPALTest(unittest.TestCase):
self.assertEqual(cpal.version, 0)
self.assertEqual(cpal.numPaletteEntries, 2)
self.assertEqual(repr(cpal.palettes), '[[#12345678, #FEDCBA98]]')
- self.assertEqual(cpal.paletteLabels, [0])
- self.assertEqual(cpal.paletteTypes, [0])
- self.assertEqual(cpal.paletteEntryLabels, [0, 0])
def test_fromXML_v1(self):
cpal = newTable('CPAL')
@@ -219,7 +209,8 @@ class CPALTest(unittest.TestCase):
'[[#12345678, #FEDCBA98, #CAFECAFE]]')
self.assertEqual(cpal.paletteLabels, [259])
self.assertEqual(cpal.paletteTypes, [2])
- self.assertEqual(cpal.paletteEntryLabels, [0, 262, 0])
+ self.assertEqual(cpal.paletteEntryLabels,
+ [cpal.NO_NAME_ID, 262, cpal.NO_NAME_ID])
if __name__ == "__main__":
diff --git a/Tests/ttLib/tables/M_V_A_R_test.py b/Tests/ttLib/tables/M_V_A_R_test.py
index 05a92e84..3972d8c3 100644
--- a/Tests/ttLib/tables/M_V_A_R_test.py
+++ b/Tests/ttLib/tables/M_V_A_R_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib.tables._f_v_a_r import Axis
diff --git a/Tests/ttLib/tables/O_S_2f_2_test.py b/Tests/ttLib/tables/O_S_2f_2_test.py
index 116e82ea..1f123090 100644
--- a/Tests/ttLib/tables/O_S_2f_2_test.py
+++ b/Tests/ttLib/tables/O_S_2f_2_test.py
@@ -1,4 +1,3 @@
-from __future__ import print_function, division, absolute_import
from fontTools.ttLib import TTFont, newTable, getTableModule
from fontTools.ttLib.tables.O_S_2f_2 import *
import unittest
diff --git a/Tests/ttLib/tables/S_T_A_T_test.py b/Tests/ttLib/tables/S_T_A_T_test.py
index e851f98f..c5c12341 100644
--- a/Tests/ttLib/tables/S_T_A_T_test.py
+++ b/Tests/ttLib/tables/S_T_A_T_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr
from fontTools.ttLib import newTable
@@ -148,7 +146,7 @@ STAT_XML_AXIS_VALUE_FORMAT3 = [
'<AxisValueArray>',
' <AxisValue index="0" Format="3">',
' <AxisIndex value="0"/>',
- ' <Flags value="2"/>',
+ ' <Flags value="2"/> <!-- ElidableAxisValueName -->',
' <ValueNameID value="2"/>',
' <Value value="400.0"/>',
' <LinkedValue value="700.0"/>',
@@ -192,7 +190,7 @@ STAT_XML_VERSION_1_1 = [
'<AxisValueArray>',
' <AxisValue index="0" Format="3">',
' <AxisIndex value="0"/>',
- ' <Flags value="2"/>',
+ ' <Flags value="2"/> <!-- ElidableAxisValueName -->',
' <ValueNameID value="2"/>',
' <Value value="400.0"/>',
' <LinkedValue value="700.0"/>',
diff --git a/Tests/ttLib/tables/T_S_I__0_test.py b/Tests/ttLib/tables/T_S_I__0_test.py
index 1de6b341..44ca44ed 100644
--- a/Tests/ttLib/tables/T_S_I__0_test.py
+++ b/Tests/ttLib/tables/T_S_I__0_test.py
@@ -1,5 +1,4 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import SimpleNamespace
+from types import SimpleNamespace
from fontTools.misc.textTools import deHexStr
from fontTools.misc.testTools import getXML
from fontTools.ttLib.tables.T_S_I__0 import table_T_S_I__0
diff --git a/Tests/ttLib/tables/T_S_I__1_test.py b/Tests/ttLib/tables/T_S_I__1_test.py
index e529425a..3a565adc 100644
--- a/Tests/ttLib/tables/T_S_I__1_test.py
+++ b/Tests/ttLib/tables/T_S_I__1_test.py
@@ -1,7 +1,4 @@
-from __future__ import (
- print_function, division, absolute_import, unicode_literals
-)
-from fontTools.misc.py23 import unichr, tobytes
+from fontTools.misc.py23 import tobytes
from fontTools.misc.loggingTools import CapturingLogHandler
from fontTools.ttLib import TTFont, TTLibError
from fontTools.ttLib.tables.T_S_I__0 import table_T_S_I__0
@@ -36,7 +33,7 @@ def font(indextable):
# ['a', 'b', 'c', ...]
ch = 0x61
n = len(indextable.indices)
- font.glyphOrder = [unichr(i) for i in range(ch, ch+n)]
+ font.glyphOrder = [chr(i) for i in range(ch, ch+n)]
font['TSI0'] = indextable
return font
diff --git a/Tests/ttLib/tables/TupleVariation_test.py b/Tests/ttLib/tables/TupleVariation_test.py
index d78810a9..d7a0bc8a 100644
--- a/Tests/ttLib/tables/TupleVariation_test.py
+++ b/Tests/ttLib/tables/TupleVariation_test.py
@@ -1,6 +1,3 @@
-from __future__ import \
- print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.loggingTools import CapturingLogHandler
from fontTools.misc.testTools import parseXML
from fontTools.misc.textTools import deHexStr, hexStr
@@ -8,6 +5,7 @@ from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib.tables.TupleVariation import \
log, TupleVariation, compileSharedTuples, decompileSharedTuples, \
compileTupleVariationStore, decompileTupleVariationStore, inferRegion_
+from io import BytesIO
import random
import unittest
@@ -18,9 +16,9 @@ def hexencode(s):
AXES = {
- "wdth": (0.3, 0.4, 0.5),
+ "wdth": (0.25, 0.375, 0.5),
"wght": (0.0, 1.0, 1.0),
- "opsz": (-0.7, -0.7, 0.0)
+ "opsz": (-0.75, -0.75, 0.0)
}
@@ -114,7 +112,7 @@ class TupleVariationTest(unittest.TestCase):
self.assertIn("bad delta format", [r.msg for r in captor.records])
self.assertEqual([
'<tuple>',
- '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>',
+ '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
'<!-- bad delta #0 -->',
'</tuple>',
], TupleVariationTest.xml_lines(writer))
@@ -125,9 +123,9 @@ class TupleVariationTest(unittest.TestCase):
g.toXML(writer, ["wdth", "wght", "opsz"])
self.assertEqual([
'<tuple>',
- '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>',
+ '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
'<coord axis="wght" value="1.0"/>',
- '<coord axis="opsz" value="-0.7"/>',
+ '<coord axis="opsz" value="-0.75"/>',
'<delta cvt="0" value="42"/>',
'<delta cvt="2" value="23"/>',
'<delta cvt="3" value="0"/>',
@@ -141,9 +139,9 @@ class TupleVariationTest(unittest.TestCase):
g.toXML(writer, ["wdth", "wght", "opsz"])
self.assertEqual([
'<tuple>',
- '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>',
+ '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
'<coord axis="wght" value="1.0"/>',
- '<coord axis="opsz" value="-0.7"/>',
+ '<coord axis="opsz" value="-0.75"/>',
'<delta pt="0" x="9" y="8"/>',
'<delta pt="2" x="7" y="6"/>',
'<delta pt="3" x="0" y="0"/>',
@@ -163,6 +161,22 @@ class TupleVariationTest(unittest.TestCase):
'</tuple>'
], TupleVariationTest.xml_lines(writer))
+ def test_toXML_axes_floats(self):
+ writer = XMLWriter(BytesIO())
+ axes = {
+ "wght": (0.0, 0.2999878, 0.7000122),
+ "wdth": (0.0, 0.4000244, 0.4000244),
+ }
+ g = TupleVariation(axes, [None] * 5)
+ g.toXML(writer, ["wght", "wdth"])
+ self.assertEqual(
+ [
+ '<coord axis="wght" min="0.0" value="0.3" max="0.7"/>',
+ '<coord axis="wdth" value="0.4"/>',
+ ],
+ TupleVariationTest.xml_lines(writer)[1:3]
+ )
+
def test_fromXML_badDeltaFormat(self):
g = TupleVariation({}, [])
with CapturingLogHandler(log, "WARNING") as captor:
@@ -174,9 +188,9 @@ class TupleVariationTest(unittest.TestCase):
def test_fromXML_constants(self):
g = TupleVariation({}, [None] * 4)
for name, attrs, content in parseXML(
- '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>'
+ '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>'
'<coord axis="wght" value="1.0"/>'
- '<coord axis="opsz" value="-0.7"/>'
+ '<coord axis="opsz" value="-0.75"/>'
'<delta cvt="1" value="42"/>'
'<delta cvt="2" value="-23"/>'):
g.fromXML(name, attrs, content)
@@ -186,15 +200,31 @@ class TupleVariationTest(unittest.TestCase):
def test_fromXML_points(self):
g = TupleVariation({}, [None] * 4)
for name, attrs, content in parseXML(
- '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>'
+ '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>'
'<coord axis="wght" value="1.0"/>'
- '<coord axis="opsz" value="-0.7"/>'
+ '<coord axis="opsz" value="-0.75"/>'
'<delta pt="1" x="33" y="44"/>'
'<delta pt="2" x="-2" y="170"/>'):
g.fromXML(name, attrs, content)
self.assertEqual(AXES, g.axes)
self.assertEqual([None, (33, 44), (-2, 170), None], g.coordinates)
+ def test_fromXML_axes_floats(self):
+ g = TupleVariation({}, [None] * 4)
+ for name, attrs, content in parseXML(
+ '<coord axis="wght" min="0.0" value="0.3" max="0.7"/>'
+ '<coord axis="wdth" value="0.4"/>'
+ ):
+ g.fromXML(name, attrs, content)
+
+ self.assertEqual(g.axes["wght"][0], 0)
+ self.assertAlmostEqual(g.axes["wght"][1], 0.2999878)
+ self.assertAlmostEqual(g.axes["wght"][2], 0.7000122)
+
+ self.assertEqual(g.axes["wdth"][0], 0)
+ self.assertAlmostEqual(g.axes["wdth"][1], 0.4000244)
+ self.assertAlmostEqual(g.axes["wdth"][2], 0.4000244)
+
def test_compile_sharedPeaks_nonIntermediate_sharedPoints(self):
var = TupleVariation(
{"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
diff --git a/Tests/ttLib/tables/_a_n_k_r_test.py b/Tests/ttLib/tables/_a_n_k_r_test.py
index 0327e46b..6c9be16d 100644
--- a/Tests/ttLib/tables/_a_n_k_r_test.py
+++ b/Tests/ttLib/tables/_a_n_k_r_test.py
@@ -1,6 +1,4 @@
# coding: utf-8
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import newTable
diff --git a/Tests/ttLib/tables/_a_v_a_r_test.py b/Tests/ttLib/tables/_a_v_a_r_test.py
index 7b921188..429ca2e8 100644
--- a/Tests/ttLib/tables/_a_v_a_r_test.py
+++ b/Tests/ttLib/tables/_a_v_a_r_test.py
@@ -1,13 +1,10 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import parseXML
from fontTools.misc.textTools import deHexStr
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib import TTLibError
from fontTools.ttLib.tables._a_v_a_r import table__a_v_a_r
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis
-import collections
-import logging
+from io import BytesIO
import unittest
@@ -18,6 +15,17 @@ TEST_DATA = deHexStr(
class AxisVariationTableTest(unittest.TestCase):
+ def assertAvarAlmostEqual(self, segments1, segments2):
+ self.assertSetEqual(set(segments1.keys()), set(segments2.keys()))
+ for axisTag, mapping1 in segments1.items():
+ mapping2 = segments2[axisTag]
+ self.assertEqual(len(mapping1), len(mapping2))
+ for (k1, v1), (k2, v2) in zip(
+ sorted(mapping1.items()), sorted(mapping2.items())
+ ):
+ self.assertAlmostEqual(k1, k2)
+ self.assertAlmostEqual(v1, v2)
+
def test_compile(self):
avar = table__a_v_a_r()
avar.segments["wdth"] = {-1.0: -1.0, 0.0: 0.0, 0.3: 0.8, 1.0: 1.0}
@@ -27,8 +35,8 @@ class AxisVariationTableTest(unittest.TestCase):
def test_decompile(self):
avar = table__a_v_a_r()
avar.decompile(TEST_DATA, self.makeFont(["wdth", "wght"]))
- self.assertEqual({
- "wdth": {-1.0: -1.0, 0.0: 0.0, 0.3: 0.8, 1.0: 1.0},
+ self.assertAvarAlmostEqual({
+ "wdth": {-1.0: -1.0, 0.0: 0.0, 0.2999878: 0.7999878, 1.0: 1.0},
"wght": {-1.0: -1.0, 0.0: 0.0, 1.0: 1.0}
}, avar.segments)
@@ -39,7 +47,7 @@ class AxisVariationTableTest(unittest.TestCase):
def test_toXML(self):
avar = table__a_v_a_r()
- avar.segments["opsz"] = {-1.0: -1.0, 0.0: 0.0, 0.3: 0.8, 1.0: 1.0}
+ avar.segments["opsz"] = {-1.0: -1.0, 0.0: 0.0, 0.2999878: 0.7999878, 1.0: 1.0}
writer = XMLWriter(BytesIO())
avar.toXML(writer, self.makeFont(["opsz"]))
self.assertEqual([
@@ -61,8 +69,10 @@ class AxisVariationTableTest(unittest.TestCase):
' <mapping from="1.0" to="1.0"/>'
'</segment>'):
avar.fromXML(name, attrs, content, ttFont=None)
- self.assertEqual({"wdth": {-1: -1, 0: 0, 0.7: 0.2, 1.0: 1.0}},
- avar.segments)
+ self.assertAvarAlmostEqual(
+ {"wdth": {-1: -1, 0: 0, 0.7000122: 0.2000122, 1.0: 1.0}},
+ avar.segments
+ )
@staticmethod
def makeFont(axisTags):
diff --git a/Tests/ttLib/tables/_b_s_l_n_test.py b/Tests/ttLib/tables/_b_s_l_n_test.py
index 97ffa6a8..e40c1bd2 100644
--- a/Tests/ttLib/tables/_b_s_l_n_test.py
+++ b/Tests/ttLib/tables/_b_s_l_n_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import newTable
diff --git a/Tests/ttLib/tables/_c_i_d_g_test.py b/Tests/ttLib/tables/_c_i_d_g_test.py
index c6e8d3cf..11c1fc0f 100644
--- a/Tests/ttLib/tables/_c_i_d_g_test.py
+++ b/Tests/ttLib/tables/_c_i_d_g_test.py
@@ -1,6 +1,3 @@
-# coding: utf-8
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import newTable
diff --git a/Tests/ttLib/tables/_c_m_a_p_test.py b/Tests/ttLib/tables/_c_m_a_p_test.py
index 233cd14f..63285045 100644
--- a/Tests/ttLib/tables/_c_m_a_p_test.py
+++ b/Tests/ttLib/tables/_c_m_a_p_test.py
@@ -1,8 +1,6 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
import io
import os
import re
-from fontTools.misc.py23 import *
from fontTools import ttLib
from fontTools.fontBuilder import FontBuilder
import unittest
diff --git a/Tests/ttLib/tables/_c_v_a_r_test.py b/Tests/ttLib/tables/_c_v_a_r_test.py
index 0fd55313..31c19538 100644
--- a/Tests/ttLib/tables/_c_v_a_r_test.py
+++ b/Tests/ttLib/tables/_c_v_a_r_test.py
@@ -1,6 +1,3 @@
-from __future__ import \
- print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import TTLibError, getTableModule, newTable
@@ -53,12 +50,23 @@ CVAR_XML = [
CVAR_VARIATIONS = [
TupleVariation({"wght": (0.0, 1.0, 1.0)}, [None, None, 3, 1, 4]),
- TupleVariation({"wght": (-1, -1.0, 0.0), "wdth": (0.0, 0.8, 0.8)},
+ TupleVariation({"wght": (-1, -1.0, 0.0), "wdth": (0.0, 0.7999878, 0.7999878)},
[None, None, 9, 7, 8]),
]
class CVARTableTest(unittest.TestCase):
+ def assertVariationsAlmostEqual(self, variations1, variations2):
+ self.assertEqual(len(variations1), len(variations2))
+ for (v1, v2) in zip(variations1, variations2):
+ self.assertSetEqual(set(v1.axes), set(v2.axes))
+ for axisTag, support1 in v1.axes.items():
+ support2 = v2.axes[axisTag]
+ self.assertEqual(len(support1), len(support2))
+ for s1, s2 in zip(support1, support2):
+ self.assertAlmostEqual(s1, s2)
+ self.assertEqual(v1.coordinates, v2.coordinates)
+
def makeFont(self):
cvt, cvar, fvar = newTable("cvt "), newTable("cvar"), newTable("fvar")
font = {"cvt ": cvt, "cvar": cvar, "fvar": fvar}
@@ -83,14 +91,14 @@ class CVARTableTest(unittest.TestCase):
cvar.decompile(CVAR_PRIVATE_POINT_DATA, font)
self.assertEqual(cvar.majorVersion, 1)
self.assertEqual(cvar.minorVersion, 0)
- self.assertEqual(cvar.variations, CVAR_VARIATIONS)
+ self.assertVariationsAlmostEqual(cvar.variations, CVAR_VARIATIONS)
def test_decompile_shared_points(self):
font, cvar = self.makeFont()
cvar.decompile(CVAR_DATA, font)
self.assertEqual(cvar.majorVersion, 1)
self.assertEqual(cvar.minorVersion, 0)
- self.assertEqual(cvar.variations, CVAR_VARIATIONS)
+ self.assertVariationsAlmostEqual(cvar.variations, CVAR_VARIATIONS)
def test_fromXML(self):
font, cvar = self.makeFont()
@@ -98,7 +106,7 @@ class CVARTableTest(unittest.TestCase):
cvar.fromXML(name, attrs, content, ttFont=font)
self.assertEqual(cvar.majorVersion, 1)
self.assertEqual(cvar.minorVersion, 0)
- self.assertEqual(cvar.variations, CVAR_VARIATIONS)
+ self.assertVariationsAlmostEqual(cvar.variations, CVAR_VARIATIONS)
def test_toXML(self):
font, cvar = self.makeFont()
diff --git a/Tests/ttLib/tables/_f_p_g_m_test.py b/Tests/ttLib/tables/_f_p_g_m_test.py
index 71fec481..ff233dd9 100644
--- a/Tests/ttLib/tables/_f_p_g_m_test.py
+++ b/Tests/ttLib/tables/_f_p_g_m_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.ttLib.tables._f_p_g_m import table__f_p_g_m
from fontTools.ttLib.tables import ttProgram
diff --git a/Tests/ttLib/tables/_f_v_a_r_test.py b/Tests/ttLib/tables/_f_v_a_r_test.py
index 4a8e7671..2ac5237f 100644
--- a/Tests/ttLib/tables/_f_v_a_r_test.py
+++ b/Tests/ttLib/tables/_f_v_a_r_test.py
@@ -1,11 +1,10 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import parseXML
from fontTools.misc.textTools import deHexStr
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib import TTLibError
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance
from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e, NameRecord
+from io import BytesIO
import unittest
@@ -120,7 +119,7 @@ class AxisTest(unittest.TestCase):
self.assertEqual("opsz", axis.axisTag)
self.assertEqual(345, axis.axisNameID)
self.assertEqual(-0.5, axis.minValue)
- self.assertEqual(1.3, axis.defaultValue)
+ self.assertAlmostEqual(1.3000031, axis.defaultValue)
self.assertEqual(1.5, axis.maxValue)
def test_toXML(self):
@@ -166,6 +165,11 @@ class AxisTest(unittest.TestCase):
class NamedInstanceTest(unittest.TestCase):
+ def assertDictAlmostEqual(self, dict1, dict2):
+ self.assertEqual(set(dict1.keys()), set(dict2.keys()))
+ for key in dict1:
+ self.assertAlmostEqual(dict1[key], dict2[key])
+
def test_compile_withPostScriptName(self):
inst = NamedInstance()
inst.subfamilyNameID = 345
@@ -187,14 +191,14 @@ class NamedInstanceTest(unittest.TestCase):
inst.decompile(FVAR_INSTANCE_DATA_WITH_PSNAME, ["wght", "wdth"])
self.assertEqual(564, inst.postscriptNameID)
self.assertEqual(345, inst.subfamilyNameID)
- self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
+ self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)
def test_decompile_withoutPostScriptName(self):
inst = NamedInstance()
inst.decompile(FVAR_INSTANCE_DATA_WITHOUT_PSNAME, ["wght", "wdth"])
self.assertEqual(0xFFFF, inst.postscriptNameID)
self.assertEqual(345, inst.subfamilyNameID)
- self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
+ self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)
def test_toXML_withPostScriptName(self):
font = MakeFont()
@@ -244,7 +248,7 @@ class NamedInstanceTest(unittest.TestCase):
inst.fromXML(name, attrs, content, ttFont=MakeFont())
self.assertEqual(257, inst.postscriptNameID)
self.assertEqual(345, inst.subfamilyNameID)
- self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
+ self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)
def test_fromXML_withoutPostScriptName(self):
inst = NamedInstance()
@@ -256,7 +260,7 @@ class NamedInstanceTest(unittest.TestCase):
inst.fromXML(name, attrs, content, ttFont=MakeFont())
self.assertEqual(0x123ABC, inst.flags)
self.assertEqual(345, inst.subfamilyNameID)
- self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
+ self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)
if __name__ == "__main__":
diff --git a/Tests/ttLib/tables/_g_c_i_d_test.py b/Tests/ttLib/tables/_g_c_i_d_test.py
index 1ec92fb3..e7666771 100644
--- a/Tests/ttLib/tables/_g_c_i_d_test.py
+++ b/Tests/ttLib/tables/_g_c_i_d_test.py
@@ -1,6 +1,3 @@
-# coding: utf-8
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import newTable
diff --git a/Tests/ttLib/tables/_g_l_y_f_test.py b/Tests/ttLib/tables/_g_l_y_f_test.py
index b30528aa..531bb82a 100644
--- a/Tests/ttLib/tables/_g_l_y_f_test.py
+++ b/Tests/ttLib/tables/_g_l_y_f_test.py
@@ -1,12 +1,25 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.fixedTools import otRound
+from fontTools.misc.testTools import getXML, parseXML
from fontTools.pens.ttGlyphPen import TTGlyphPen
+from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen
+from fontTools.pens.pointPen import PointToSegmentPen
from fontTools.ttLib import TTFont, newTable, TTLibError
-from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
+from fontTools.ttLib.tables._g_l_y_f import (
+ Glyph,
+ GlyphCoordinates,
+ GlyphComponent,
+ ARGS_ARE_XY_VALUES,
+ SCALED_COMPONENT_OFFSET,
+ UNSCALED_COMPONENT_OFFSET,
+ WE_HAVE_A_SCALE,
+ WE_HAVE_A_TWO_BY_TWO,
+ WE_HAVE_AN_X_AND_Y_SCALE,
+)
from fontTools.ttLib.tables import ttProgram
import sys
import array
+from io import StringIO
+import itertools
import pytest
import re
import os
@@ -180,7 +193,7 @@ def strip_ttLibVersion(string):
return re.sub(' ttLibVersion=".*"', '', string)
-class glyfTableTest(unittest.TestCase):
+class GlyfTableTest(unittest.TestCase):
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
@@ -212,7 +225,7 @@ class glyfTableTest(unittest.TestCase):
font['head'].decompile(self.headData, font)
font['loca'].decompile(self.locaData, font)
glyfTable.decompile(self.glyfData, font)
- out = UnicodeIO()
+ out = StringIO()
font.saveXML(out)
glyfXML = strip_ttLibVersion(out.getvalue()).splitlines()
self.assertEqual(glyfXML, self.glyfXML)
@@ -276,6 +289,353 @@ class glyfTableTest(unittest.TestCase):
composite.compact(glyfTable)
+ def test_bit6_draw_to_pen_issue1771(self):
+ # https://github.com/fonttools/fonttools/issues/1771
+ font = TTFont(sfntVersion="\x00\x01\x00\x00")
+ # glyph00003 contains a bit 6 flag on the first point,
+ # which triggered the issue
+ font.importXML(GLYF_TTX)
+ glyfTable = font['glyf']
+ pen = RecordingPen()
+ glyfTable["glyph00003"].draw(pen, glyfTable=glyfTable)
+ expected = [('moveTo', ((501, 1430),)),
+ ('lineTo', ((683, 1430),)),
+ ('lineTo', ((1172, 0),)),
+ ('lineTo', ((983, 0),)),
+ ('lineTo', ((591, 1193),)),
+ ('lineTo', ((199, 0),)),
+ ('lineTo', ((12, 0),)),
+ ('closePath', ()),
+ ('moveTo', ((249, 514),)),
+ ('lineTo', ((935, 514),)),
+ ('lineTo', ((935, 352),)),
+ ('lineTo', ((249, 352),)),
+ ('closePath', ())]
+ self.assertEqual(pen.value, expected)
+
+ def test_bit6_draw_to_pointpen(self):
+ # https://github.com/fonttools/fonttools/issues/1771
+ font = TTFont(sfntVersion="\x00\x01\x00\x00")
+ # glyph00003 contains a bit 6 flag on the first point
+ # which triggered the issue
+ font.importXML(GLYF_TTX)
+ glyfTable = font['glyf']
+ pen = RecordingPointPen()
+ glyfTable["glyph00003"].drawPoints(pen, glyfTable=glyfTable)
+ expected = [
+ ('beginPath', (), {}),
+ ('addPoint', ((501, 1430), 'line', False, None), {}),
+ ('addPoint', ((683, 1430), 'line', False, None), {}),
+ ('addPoint', ((1172, 0), 'line', False, None), {}),
+ ('addPoint', ((983, 0), 'line', False, None), {}),
+ ]
+ self.assertEqual(pen.value[:len(expected)], expected)
+
+ def test_draw_vs_drawpoints(self):
+ font = TTFont(sfntVersion="\x00\x01\x00\x00")
+ font.importXML(GLYF_TTX)
+ glyfTable = font['glyf']
+ pen1 = RecordingPen()
+ pen2 = RecordingPen()
+ glyfTable["glyph00003"].draw(pen1, glyfTable)
+ glyfTable["glyph00003"].drawPoints(PointToSegmentPen(pen2), glyfTable)
+ self.assertEqual(pen1.value, pen2.value)
+
+ def test_compile_empty_table(self):
+ font = TTFont(sfntVersion="\x00\x01\x00\x00")
+ font.importXML(GLYF_TTX)
+ glyfTable = font['glyf']
+ # set all glyphs to zero contours
+ glyfTable.glyphs = {glyphName: Glyph() for glyphName in font.getGlyphOrder()}
+ glyfData = glyfTable.compile(font)
+ self.assertEqual(glyfData, b"\x00")
+ self.assertEqual(list(font["loca"]), [0] * (font["maxp"].numGlyphs+1))
+
+ def test_decompile_empty_table(self):
+ font = TTFont()
+ glyphNames = [".notdef", "space"]
+ font.setGlyphOrder(glyphNames)
+ font["loca"] = newTable("loca")
+ font["loca"].locations = [0] * (len(glyphNames) + 1)
+ font["glyf"] = newTable("glyf")
+ font["glyf"].decompile(b"\x00", font)
+ self.assertEqual(len(font["glyf"]), 2)
+ self.assertEqual(font["glyf"][".notdef"].numberOfContours, 0)
+ self.assertEqual(font["glyf"]["space"].numberOfContours, 0)
+
+
+class GlyphTest:
+
+ def test_getCoordinates(self):
+ glyphSet = {}
+ pen = TTGlyphPen(glyphSet)
+ pen.moveTo((0, 0))
+ pen.lineTo((100, 0))
+ pen.lineTo((100, 100))
+ pen.lineTo((0, 100))
+ pen.closePath()
+ # simple contour glyph
+ glyphSet["a"] = a = pen.glyph()
+
+ assert a.getCoordinates(glyphSet) == (
+ GlyphCoordinates([(0, 0), (100, 0), (100, 100), (0, 100)]),
+ [3],
+ array.array("B", [1, 1, 1, 1]),
+ )
+
+ # composite glyph with only XY offset
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("a", (1, 0, 0, 1, 10, 20))
+ glyphSet["b"] = b = pen.glyph()
+
+ assert b.getCoordinates(glyphSet) == (
+ GlyphCoordinates([(10, 20), (110, 20), (110, 120), (10, 120)]),
+ [3],
+ array.array("B", [1, 1, 1, 1]),
+ )
+
+ # composite glyph with a scale (and referencing another composite glyph)
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("b", (0.5, 0, 0, 0.5, 0, 0))
+ glyphSet["c"] = c = pen.glyph()
+
+ assert c.getCoordinates(glyphSet) == (
+ GlyphCoordinates([(5, 10), (55, 10), (55, 60), (5, 60)]),
+ [3],
+ array.array("B", [1, 1, 1, 1]),
+ )
+
+ # composite glyph with unscaled offset (MS-style)
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("a", (0.5, 0, 0, 0.5, 10, 20))
+ glyphSet["d"] = d = pen.glyph()
+ d.components[0].flags |= UNSCALED_COMPONENT_OFFSET
+
+ assert d.getCoordinates(glyphSet) == (
+ GlyphCoordinates([(10, 20), (60, 20), (60, 70), (10, 70)]),
+ [3],
+ array.array("B", [1, 1, 1, 1]),
+ )
+
+ # composite glyph with a scaled offset (Apple-style)
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("a", (0.5, 0, 0, 0.5, 10, 20))
+ glyphSet["e"] = e = pen.glyph()
+ e.components[0].flags |= SCALED_COMPONENT_OFFSET
+
+ assert e.getCoordinates(glyphSet) == (
+ GlyphCoordinates([(5, 10), (55, 10), (55, 60), (5, 60)]),
+ [3],
+ array.array("B", [1, 1, 1, 1]),
+ )
+
+ # composite glyph where the 2nd and 3rd components use anchor points
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("a", (1, 0, 0, 1, 0, 0))
+ glyphSet["f"] = f = pen.glyph()
+
+ comp1 = GlyphComponent()
+ comp1.glyphName = "a"
+ # aling the new component's pt 0 to pt 2 of contour points added so far
+ comp1.firstPt = 2
+ comp1.secondPt = 0
+ comp1.flags = 0
+ f.components.append(comp1)
+
+ comp2 = GlyphComponent()
+ comp2.glyphName = "a"
+ # aling the new component's pt 0 to pt 6 of contour points added so far
+ comp2.firstPt = 6
+ comp2.secondPt = 0
+ comp2.transform = [[0.707107, 0.707107], [-0.707107, 0.707107]] # rotate 45 deg
+ comp2.flags = WE_HAVE_A_TWO_BY_TWO
+ f.components.append(comp2)
+
+ coords, end_pts, flags = f.getCoordinates(glyphSet)
+ assert end_pts == [3, 7, 11]
+ assert flags == array.array("B", [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
+ assert list(sum(coords, ())) == pytest.approx(
+ [
+ 0, 0,
+ 100, 0,
+ 100, 100,
+ 0, 100,
+ 100, 100,
+ 200, 100,
+ 200, 200,
+ 100, 200,
+ 200, 200,
+ 270.7107, 270.7107,
+ 200.0, 341.4214,
+ 129.2893, 270.7107,
+ ]
+ )
+
+ def test_getCompositeMaxpValues(self):
+ # https://github.com/fonttools/fonttools/issues/2044
+ glyphSet = {}
+ pen = TTGlyphPen(glyphSet) # empty non-composite glyph
+ glyphSet["fraction"] = pen.glyph()
+ glyphSet["zero.numr"] = pen.glyph()
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
+ glyphSet["zero.dnom"] = pen.glyph()
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
+ pen.addComponent("fraction", (1, 0, 0, 1, 0, 0))
+ pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
+ glyphSet["percent"] = pen.glyph()
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
+ pen.addComponent("fraction", (1, 0, 0, 1, 0, 0))
+ pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
+ pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
+ glyphSet["perthousand"] = pen.glyph()
+ assert glyphSet["zero.dnom"].getCompositeMaxpValues(glyphSet)[2] == 1
+ assert glyphSet["percent"].getCompositeMaxpValues(glyphSet)[2] == 2
+ assert glyphSet["perthousand"].getCompositeMaxpValues(glyphSet)[2] == 2
+
+
+class GlyphComponentTest:
+
+ def test_toXML_no_transform(self):
+ comp = GlyphComponent()
+ comp.glyphName = "a"
+ comp.flags = ARGS_ARE_XY_VALUES
+ comp.x, comp.y = 1, 2
+
+ assert getXML(comp.toXML) == [
+ '<component glyphName="a" x="1" y="2" flags="0x2"/>'
+ ]
+
+ def test_toXML_transform_scale(self):
+ comp = GlyphComponent()
+ comp.glyphName = "a"
+ comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_A_SCALE
+ comp.x, comp.y = 1, 2
+
+ comp.transform = [[0.2999878, 0], [0, 0.2999878]]
+ assert getXML(comp.toXML) == [
+ '<component glyphName="a" x="1" y="2" scale="0.3" flags="0xa"/>'
+ ]
+
+ def test_toXML_transform_xy_scale(self):
+ comp = GlyphComponent()
+ comp.glyphName = "a"
+ comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_AN_X_AND_Y_SCALE
+ comp.x, comp.y = 1, 2
+
+ comp.transform = [[0.5999756, 0], [0, 0.2999878]]
+ assert getXML(comp.toXML) == [
+ '<component glyphName="a" x="1" y="2" scalex="0.6" '
+ 'scaley="0.3" flags="0x42"/>'
+ ]
+
+ def test_toXML_transform_2x2_scale(self):
+ comp = GlyphComponent()
+ comp.glyphName = "a"
+ comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_A_TWO_BY_TWO
+ comp.x, comp.y = 1, 2
+
+ comp.transform = [[0.5999756, -0.2000122], [0.2000122, 0.2999878]]
+ assert getXML(comp.toXML) == [
+ '<component glyphName="a" x="1" y="2" scalex="0.6" scale01="-0.2" '
+ 'scale10="0.2" scaley="0.3" flags="0x82"/>'
+ ]
+
+ def test_fromXML_no_transform(self):
+ comp = GlyphComponent()
+ for name, attrs, content in parseXML(
+ ['<component glyphName="a" x="1" y="2" flags="0x2"/>']
+ ):
+ comp.fromXML(name, attrs, content, ttFont=None)
+
+ assert comp.glyphName == "a"
+ assert comp.flags & ARGS_ARE_XY_VALUES != 0
+ assert (comp.x, comp.y) == (1, 2)
+ assert not hasattr(comp, "transform")
+
+ def test_fromXML_transform_scale(self):
+ comp = GlyphComponent()
+ for name, attrs, content in parseXML(
+ ['<component glyphName="a" x="1" y="2" scale="0.3" flags="0xa"/>']
+ ):
+ comp.fromXML(name, attrs, content, ttFont=None)
+
+ assert comp.glyphName == "a"
+ assert comp.flags & ARGS_ARE_XY_VALUES != 0
+ assert comp.flags & WE_HAVE_A_SCALE != 0
+ assert (comp.x, comp.y) == (1, 2)
+ assert hasattr(comp, "transform")
+ for value, expected in zip(
+ itertools.chain(*comp.transform), [0.2999878, 0, 0, 0.2999878]
+ ):
+ assert value == pytest.approx(expected)
+
+ def test_fromXML_transform_xy_scale(self):
+ comp = GlyphComponent()
+ for name, attrs, content in parseXML(
+ [
+ '<component glyphName="a" x="1" y="2" scalex="0.6" '
+ 'scaley="0.3" flags="0x42"/>'
+ ]
+ ):
+ comp.fromXML(name, attrs, content, ttFont=None)
+
+ assert comp.glyphName == "a"
+ assert comp.flags & ARGS_ARE_XY_VALUES != 0
+ assert comp.flags & WE_HAVE_AN_X_AND_Y_SCALE != 0
+ assert (comp.x, comp.y) == (1, 2)
+ assert hasattr(comp, "transform")
+ for value, expected in zip(
+ itertools.chain(*comp.transform), [0.5999756, 0, 0, 0.2999878]
+ ):
+ assert value == pytest.approx(expected)
+
+ def test_fromXML_transform_2x2_scale(self):
+ comp = GlyphComponent()
+ for name, attrs, content in parseXML(
+ [
+ '<component glyphName="a" x="1" y="2" scalex="0.6" scale01="-0.2" '
+ 'scale10="0.2" scaley="0.3" flags="0x82"/>'
+ ]
+ ):
+ comp.fromXML(name, attrs, content, ttFont=None)
+
+ assert comp.glyphName == "a"
+ assert comp.flags & ARGS_ARE_XY_VALUES != 0
+ assert comp.flags & WE_HAVE_A_TWO_BY_TWO != 0
+ assert (comp.x, comp.y) == (1, 2)
+ assert hasattr(comp, "transform")
+ for value, expected in zip(
+ itertools.chain(*comp.transform),
+ [0.5999756, -0.2000122, 0.2000122, 0.2999878]
+ ):
+ assert value == pytest.approx(expected)
+
+ def test_toXML_reference_points(self):
+ comp = GlyphComponent()
+ comp.glyphName = "a"
+ comp.flags = 0
+ comp.firstPt = 1
+ comp.secondPt = 2
+
+ assert getXML(comp.toXML) == [
+ '<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>'
+ ]
+
+ def test_fromXML_reference_points(self):
+ comp = GlyphComponent()
+ for name, attrs, content in parseXML(
+ ['<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>']
+ ):
+ comp.fromXML(name, attrs, content, ttFont=None)
+
+ assert comp.glyphName == "a"
+ assert comp.flags == 0
+ assert (comp.firstPt, comp.secondPt) == (1, 2)
+ assert not hasattr(comp, "transform")
+
if __name__ == "__main__":
import sys
diff --git a/Tests/ttLib/tables/_g_v_a_r_test.py b/Tests/ttLib/tables/_g_v_a_r_test.py
index ebae5806..077bb639 100644
--- a/Tests/ttLib/tables/_g_v_a_r_test.py
+++ b/Tests/ttLib/tables/_g_v_a_r_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import TTLibError, getTableClass, getTableModule, newTable
@@ -65,7 +63,7 @@ GVAR_VARIATIONS = {
],
"space": [
TupleVariation(
- {"wdth": (0.0, 0.7, 0.7)},
+ {"wdth": (0.0, 0.7000122, 0.7000122)},
[(1, 11), (2, 22), (3, 33), (4, 44)]),
],
"I": [
@@ -73,7 +71,7 @@ GVAR_VARIATIONS = {
{"wght": (0.0, 0.5, 1.0)},
[(3,3), (1,1), (4,4), (1,1), (5,5), (9,9), (2,2), (6,6)]),
TupleVariation(
- {"wght": (-1.0, -1.0, 0.0), "wdth": (0.0, 0.8, 0.8)},
+ {"wght": (-1.0, -1.0, 0.0), "wdth": (0.0, 0.7999878, 0.7999878)},
[(-8,-88), (7,77), None, None, (-4,44), (3,33), (-2,-22), (1,11)]),
],
}
@@ -82,15 +80,6 @@ GVAR_VARIATIONS = {
GVAR_XML = [
'<version value="1"/>',
'<reserved value="0"/>',
- '<glyphVariations glyph="space">',
- ' <tuple>',
- ' <coord axis="wdth" value="0.7"/>',
- ' <delta pt="0" x="1" y="11"/>',
- ' <delta pt="1" x="2" y="22"/>',
- ' <delta pt="2" x="3" y="33"/>',
- ' <delta pt="3" x="4" y="44"/>',
- ' </tuple>',
- '</glyphVariations>',
'<glyphVariations glyph="I">',
' <tuple>',
' <coord axis="wght" min="0.0" value="0.5" max="1.0"/>',
@@ -114,6 +103,15 @@ GVAR_XML = [
' <delta pt="7" x="1" y="11"/>',
' </tuple>',
'</glyphVariations>',
+ '<glyphVariations glyph="space">',
+ ' <tuple>',
+ ' <coord axis="wdth" value="0.7"/>',
+ ' <delta pt="0" x="1" y="11"/>',
+ ' <delta pt="1" x="2" y="22"/>',
+ ' <delta pt="2" x="3" y="33"/>',
+ ' <delta pt="3" x="4" y="44"/>',
+ ' </tuple>',
+ '</glyphVariations>',
]
@@ -133,6 +131,20 @@ def hexencode(s):
class GVARTableTest(unittest.TestCase):
+ def assertVariationsAlmostEqual(self, vars1, vars2):
+ self.assertSetEqual(set(vars1.keys()), set(vars2.keys()))
+ for glyphName, variations1 in vars1.items():
+ variations2 = vars2[glyphName]
+ self.assertEqual(len(variations1), len(variations2))
+ for (v1, v2) in zip(variations1, variations2):
+ self.assertSetEqual(set(v1.axes), set(v2.axes))
+ for axisTag, support1 in v1.axes.items():
+ support2 = v2.axes[axisTag]
+ self.assertEqual(len(support1), len(support2))
+ for s1, s2 in zip(support1, support2):
+ self.assertAlmostEqual(s1, s2)
+ self.assertEqual(v1.coordinates, v2.coordinates)
+
def makeFont(self, variations):
glyphs=[".notdef", "space", "I"]
Axis = getTableModule("fvar").Axis
@@ -164,7 +176,7 @@ class GVARTableTest(unittest.TestCase):
def test_decompile(self):
font, gvar = self.makeFont({})
gvar.decompile(GVAR_DATA, font)
- self.assertEqual(gvar.variations, GVAR_VARIATIONS)
+ self.assertVariationsAlmostEqual(gvar.variations, GVAR_VARIATIONS)
def test_decompile_noVariations(self):
font, gvar = self.makeFont({})
@@ -176,8 +188,10 @@ class GVARTableTest(unittest.TestCase):
font, gvar = self.makeFont({})
for name, attrs, content in parseXML(GVAR_XML):
gvar.fromXML(name, attrs, content, ttFont=font)
- self.assertEqual(gvar.variations,
- {g:v for g,v in GVAR_VARIATIONS.items() if v})
+ self.assertVariationsAlmostEqual(
+ gvar.variations,
+ {g:v for g,v in GVAR_VARIATIONS.items() if v}
+ )
def test_toXML(self):
font, gvar = self.makeFont(GVAR_VARIATIONS)
diff --git a/Tests/ttLib/tables/_h_h_e_a_test.py b/Tests/ttLib/tables/_h_h_e_a_test.py
index 64849e9d..e04fd7bb 100644
--- a/Tests/ttLib/tables/_h_h_e_a_test.py
+++ b/Tests/ttLib/tables/_h_h_e_a_test.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.loggingTools import CapturingLogHandler
from fontTools.misc.testTools import parseXML, getXML
from fontTools.misc.textTools import deHexStr
@@ -127,6 +125,18 @@ class HheaCompileOrToXMLTest(unittest.TestCase):
len([r for r in captor.records
if "Table version value is a float" in r.msg]) == 1)
+ def test_aliases(self):
+ hhea = self.font['hhea']
+ self.assertEqual(hhea.ascent, hhea.ascender)
+ self.assertEqual(hhea.descent, hhea.descender)
+ hhea.ascender = 800
+ self.assertEqual(hhea.ascent, 800)
+ hhea.ascent = 750
+ self.assertEqual(hhea.ascender, 750)
+ hhea.descender = -300
+ self.assertEqual(hhea.descent, -300)
+ hhea.descent = -299
+ self.assertEqual(hhea.descender, -299)
class HheaDecompileOrFromXMLTest(unittest.TestCase):
diff --git a/Tests/ttLib/tables/_h_m_t_x_test.py b/Tests/ttLib/tables/_h_m_t_x_test.py
index 5c79fb88..79d0cb7e 100644
--- a/Tests/ttLib/tables/_h_m_t_x_test.py
+++ b/Tests/ttLib/tables/_h_m_t_x_test.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import parseXML, getXML
from fontTools.misc.textTools import deHexStr
from fontTools.ttLib import TTFont, newTable, TTLibError
diff --git a/Tests/ttLib/tables/_k_e_r_n_test.py b/Tests/ttLib/tables/_k_e_r_n_test.py
index b37748ad..eb48bae6 100644
--- a/Tests/ttLib/tables/_k_e_r_n_test.py
+++ b/Tests/ttLib/tables/_k_e_r_n_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import newTable
from fontTools.ttLib.tables._k_e_r_n import (
KernTable_format_0, KernTable_format_unkown)
diff --git a/Tests/ttLib/tables/_l_c_a_r_test.py b/Tests/ttLib/tables/_l_c_a_r_test.py
index 4525def7..5837a07a 100644
--- a/Tests/ttLib/tables/_l_c_a_r_test.py
+++ b/Tests/ttLib/tables/_l_c_a_r_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import newTable
diff --git a/Tests/ttLib/tables/_l_t_a_g_test.py b/Tests/ttLib/tables/_l_t_a_g_test.py
index a4c5a0ea..fc9be82a 100644
--- a/Tests/ttLib/tables/_l_t_a_g_test.py
+++ b/Tests/ttLib/tables/_l_t_a_g_test.py
@@ -1,7 +1,6 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import parseXML
from fontTools.misc.xmlWriter import XMLWriter
+from io import BytesIO
import os
import struct
import unittest
diff --git a/Tests/ttLib/tables/_m_e_t_a_test.py b/Tests/ttLib/tables/_m_e_t_a_test.py
index dc0f8cd0..f05ff576 100644
--- a/Tests/ttLib/tables/_m_e_t_a_test.py
+++ b/Tests/ttLib/tables/_m_e_t_a_test.py
@@ -1,10 +1,9 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import parseXML
from fontTools.misc.textTools import deHexStr
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib import TTLibError
from fontTools.ttLib.tables._m_e_t_a import table__m_e_t_a
+from io import BytesIO
import unittest
@@ -59,6 +58,19 @@ class MetaTableTest(unittest.TestCase):
'</hexdata>'
], [line.strip() for line in xml.splitlines()][1:])
+ def test_toXML_ascii_data(self):
+ table = table__m_e_t_a()
+ table.data["TEST"] = b"Hello!"
+ writer = XMLWriter(BytesIO())
+ table.toXML(writer, {"meta": table})
+ xml = writer.file.getvalue().decode("utf-8")
+ self.assertEqual([
+ '<hexdata tag="TEST">',
+ '<!-- ascii: Hello! -->',
+ '48656c6c 6f21',
+ '</hexdata>'
+ ], [line.strip() for line in xml.splitlines()][1:])
+
def test_fromXML(self):
table = table__m_e_t_a()
for name, attrs, content in parseXML(
diff --git a/Tests/ttLib/tables/_m_o_r_t_test.py b/Tests/ttLib/tables/_m_o_r_t_test.py
index 70696e82..3e7169be 100644
--- a/Tests/ttLib/tables/_m_o_r_t_test.py
+++ b/Tests/ttLib/tables/_m_o_r_t_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import newTable
diff --git a/Tests/ttLib/tables/_m_o_r_x_test.py b/Tests/ttLib/tables/_m_o_r_x_test.py
index f4e7fb89..0b807f82 100644
--- a/Tests/ttLib/tables/_m_o_r_x_test.py
+++ b/Tests/ttLib/tables/_m_o_r_x_test.py
@@ -1,6 +1,4 @@
-# coding: utf-8
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, bytesjoin
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import newTable
diff --git a/Tests/ttLib/tables/_n_a_m_e_test.py b/Tests/ttLib/tables/_n_a_m_e_test.py
index 136b63f3..8e829704 100644
--- a/Tests/ttLib/tables/_n_a_m_e_test.py
+++ b/Tests/ttLib/tables/_n_a_m_e_test.py
@@ -1,10 +1,9 @@
-# -*- coding: utf-8 -*-
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, tostr
from fontTools.misc import sstruct
from fontTools.misc.loggingTools import CapturingLogHandler
from fontTools.misc.testTools import FakeFont
from fontTools.misc.xmlWriter import XMLWriter
+from io import BytesIO
import struct
import unittest
from fontTools.ttLib import newTable
@@ -54,6 +53,26 @@ class NameTableTest(unittest.TestCase):
with self.assertRaises(TypeError):
table.setName(1.000, 5, 1, 0, 0)
+ def test_names_sort_bytes_str(self):
+ # Corner case: If a user appends a name record directly to `names`, the
+ # `__lt__` method on NameRecord may run into duplicate name records where
+ # one `string` is a str and the other one bytes, leading to an exception.
+ table = table__n_a_m_e()
+ table.names = [
+ makeName("Test", 25, 3, 1, 0x409),
+ makeName("Test".encode("utf-16be"), 25, 3, 1, 0x409),
+ ]
+ table.compile(None)
+
+ def test_names_sort_bytes_str_encoding_error(self):
+ table = table__n_a_m_e()
+ table.names = [
+ makeName("Test寬", 25, 1, 0, 0),
+ makeName("Test鬆鬆", 25, 1, 0, 0),
+ ]
+ with self.assertRaises(TypeError):
+ table.names.sort()
+
def test_addName(self):
table = table__n_a_m_e()
nameIDs = []
@@ -76,6 +95,111 @@ class NameTableTest(unittest.TestCase):
with self.assertRaises(TypeError):
table.addName(b"abc") # must be unicode string
+ def test_removeNames(self):
+ table = table__n_a_m_e()
+ table.setName("Regular", 2, 1, 0, 0)
+ table.setName("Regular", 2, 3, 1, 0x409)
+ table.removeNames(nameID=2)
+ self.assertEqual(table.names, [])
+
+ table = table__n_a_m_e()
+ table.setName("FamilyName", 1, 1, 0, 0)
+ table.setName("Regular", 2, 1, 0, 0)
+ table.setName("FamilyName", 1, 3, 1, 0x409)
+ table.setName("Regular", 2, 3, 1, 0x409)
+ table.removeNames(platformID=1)
+ self.assertEqual(len(table.names), 2)
+ self.assertIsNone(table.getName(1, 1, 0, 0))
+ self.assertIsNone(table.getName(2, 1, 0, 0))
+ rec1 = table.getName(1, 3, 1, 0x409)
+ self.assertEqual(str(rec1), "FamilyName")
+ rec2 = table.getName(2, 3, 1, 0x409)
+ self.assertEqual(str(rec2), "Regular")
+
+ table = table__n_a_m_e()
+ table.setName("FamilyName", 1, 1, 0, 0)
+ table.setName("Regular", 2, 1, 0, 0)
+ table.removeNames(nameID=1)
+ self.assertEqual(len(table.names), 1)
+ self.assertIsNone(table.getName(1, 1, 0, 0))
+ rec = table.getName(2, 1, 0, 0)
+ self.assertEqual(str(rec), "Regular")
+
+ table = table__n_a_m_e()
+ table.setName("FamilyName", 1, 1, 0, 0)
+ table.setName("Regular", 2, 1, 0, 0)
+ table.removeNames(2, 1, 0, 0)
+ self.assertEqual(len(table.names), 1)
+ self.assertIsNone(table.getName(2, 1, 0, 0))
+ rec = table.getName(1, 1, 0, 0)
+ self.assertEqual(str(rec), "FamilyName")
+
+ table = table__n_a_m_e()
+ table.setName("FamilyName", 1, 1, 0, 0)
+ table.setName("Regular", 2, 1, 0, 0)
+ table.removeNames()
+ self.assertEqual(len(table.names), 2)
+ rec1 = table.getName(1, 1, 0, 0)
+ self.assertEqual(str(rec1), "FamilyName")
+ rec2 = table.getName(2, 1, 0, 0)
+ self.assertEqual(str(rec2), "Regular")
+
+ @staticmethod
+ def _get_test_names():
+ names = {
+ "en": "Width",
+ "de-CH": "Breite",
+ "gsw-LI": "Bräiti",
+ }
+ namesSubSet = names.copy()
+ del namesSubSet["gsw-LI"]
+ namesSuperSet = names.copy()
+ namesSuperSet["nl"] = "Breedte"
+ return names, namesSubSet, namesSuperSet
+
+ def test_findMultilingualName(self):
+ table = table__n_a_m_e()
+ names, namesSubSet, namesSuperSet = self._get_test_names()
+ nameID = table.addMultilingualName(names)
+ assert nameID is not None
+ self.assertEqual(nameID, table.findMultilingualName(names))
+ self.assertEqual(nameID, table.findMultilingualName(namesSubSet))
+ self.assertEqual(None, table.findMultilingualName(namesSuperSet))
+
+ def test_findMultilingualName_compiled(self):
+ table = table__n_a_m_e()
+ names, namesSubSet, namesSuperSet = self._get_test_names()
+ nameID = table.addMultilingualName(names)
+ assert nameID is not None
+ # After compile/decompile, name.string is a bytes sequence, which
+ # findMultilingualName() should also handle
+ data = table.compile(None)
+ table = table__n_a_m_e()
+ table.decompile(data, None)
+ self.assertEqual(nameID, table.findMultilingualName(names))
+ self.assertEqual(nameID, table.findMultilingualName(namesSubSet))
+ self.assertEqual(None, table.findMultilingualName(namesSuperSet))
+
+ def test_addMultilingualNameReuse(self):
+ table = table__n_a_m_e()
+ names, namesSubSet, namesSuperSet = self._get_test_names()
+ nameID = table.addMultilingualName(names)
+ assert nameID is not None
+ self.assertEqual(nameID, table.addMultilingualName(names))
+ self.assertEqual(nameID, table.addMultilingualName(namesSubSet))
+ self.assertNotEqual(None, table.addMultilingualName(namesSuperSet))
+
+ def test_findMultilingualNameNoMac(self):
+ table = table__n_a_m_e()
+ names, namesSubSet, namesSuperSet = self._get_test_names()
+ nameID = table.addMultilingualName(names, mac=False)
+ assert nameID is not None
+ self.assertEqual(nameID, table.findMultilingualName(names, mac=False))
+ self.assertEqual(None, table.findMultilingualName(names))
+ self.assertEqual(nameID, table.findMultilingualName(namesSubSet, mac=False))
+ self.assertEqual(None, table.findMultilingualName(namesSubSet))
+ self.assertEqual(None, table.findMultilingualName(namesSuperSet))
+
def test_addMultilingualName(self):
# Microsoft Windows has language codes for “English” (en)
# and for “Standard German as used in Switzerland” (de-CH).
@@ -170,6 +294,17 @@ class NameTableTest(unittest.TestCase):
nameTable.addMultilingualName({"en": "A", "la": "ⱾƤℚⱤ"})
captor.assertRegex("cannot store language la into 'ltag' table")
+ def test_addMultilingualName_minNameID(self):
+ table = table__n_a_m_e()
+ names, namesSubSet, namesSuperSet = self._get_test_names()
+ nameID = table.addMultilingualName(names, nameID=2)
+ self.assertEqual(nameID, 2)
+ nameID = table.addMultilingualName(names)
+ self.assertEqual(nameID, 2)
+ nameID = table.addMultilingualName(names, minNameID=256)
+ self.assertGreaterEqual(nameID, 256)
+ self.assertEqual(nameID, table.findMultilingualName(names, minNameID=256))
+
def test_decompile_badOffset(self):
# https://github.com/fonttools/fonttools/issues/525
table = table__n_a_m_e()
@@ -203,13 +338,18 @@ class NameRecordTest(unittest.TestCase):
def test_toUnicode_macromanian(self):
name = makeName(b"Foo Italic\xfb", 222, 1, 0, 37) # Mac Romanian
self.assertEqual("mac_romanian", name.getEncoding())
- self.assertEqual("Foo Italic"+unichr(0x02DA), name.toUnicode())
+ self.assertEqual("Foo Italic"+chr(0x02DA), name.toUnicode())
def test_toUnicode_UnicodeDecodeError(self):
name = makeName(b"\1", 111, 0, 2, 7)
self.assertEqual("utf_16_be", name.getEncoding())
self.assertRaises(UnicodeDecodeError, name.toUnicode)
+ def test_toUnicode_singleChar(self):
+ # https://github.com/fonttools/fonttools/issues/1997
+ name = makeName("A", 256, 3, 1, 0x409)
+ self.assertEqual(name.toUnicode(), "A")
+
def toXML(self, name):
writer = XMLWriter(BytesIO())
name.toXML(writer, ttFont=None)
@@ -290,7 +430,19 @@ class NameRecordTest(unittest.TestCase):
def test_extended_mac_encodings(self):
name = makeName(b'\xfe', 123, 1, 1, 0) # Mac Japanese
- self.assertEqual(name.toUnicode(), unichr(0x2122))
+ self.assertEqual(name.toUnicode(), chr(0x2122))
+
+ def test_extended_mac_encodings_errors(self):
+ s = "汉仪彩云体简"
+ name = makeName(s.encode("x_mac_simp_chinese_ttx"), 123, 1, 25, 0)
+ # first check we round-trip with 'strict'
+ self.assertEqual(name.toUnicode(errors="strict"), s)
+
+ # append an incomplete invalid sequence and check that we handle
+ # errors with the requested error handler
+ name.string += b"\xba"
+ self.assertEqual(name.toUnicode(errors="backslashreplace"), s + "\\xba")
+ self.assertEqual(name.toUnicode(errors="replace"), s + "�")
def test_extended_unknown(self):
name = makeName(b'\xfe', 123, 10, 11, 12)
diff --git a/Tests/ttLib/tables/_o_p_b_d_test.py b/Tests/ttLib/tables/_o_p_b_d_test.py
index 131d3f9d..d62ada8b 100644
--- a/Tests/ttLib/tables/_o_p_b_d_test.py
+++ b/Tests/ttLib/tables/_o_p_b_d_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import newTable
diff --git a/Tests/ttLib/tables/_p_r_o_p_test.py b/Tests/ttLib/tables/_p_r_o_p_test.py
index edac9155..63c2924b 100644
--- a/Tests/ttLib/tables/_p_r_o_p_test.py
+++ b/Tests/ttLib/tables/_p_r_o_p_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import FakeFont, getXML, parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.ttLib import newTable
diff --git a/Tests/ttLib/tables/_t_r_a_k_test.py b/Tests/ttLib/tables/_t_r_a_k_test.py
index c223c815..2ea6cf59 100644
--- a/Tests/ttLib/tables/_t_r_a_k_test.py
+++ b/Tests/ttLib/tables/_t_r_a_k_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import parseXML, getXML
from fontTools.misc.textTools import deHexStr
from fontTools.ttLib import TTFont, TTLibError
diff --git a/Tests/ttLib/tables/_v_h_e_a_test.py b/Tests/ttLib/tables/_v_h_e_a_test.py
index 8979e928..c6018632 100644
--- a/Tests/ttLib/tables/_v_h_e_a_test.py
+++ b/Tests/ttLib/tables/_v_h_e_a_test.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.loggingTools import CapturingLogHandler
from fontTools.misc.testTools import parseXML, getXML
from fontTools.misc.textTools import deHexStr
diff --git a/Tests/ttLib/tables/_v_m_t_x_test.py b/Tests/ttLib/tables/_v_m_t_x_test.py
index f55dbeca..5ea2d245 100644
--- a/Tests/ttLib/tables/_v_m_t_x_test.py
+++ b/Tests/ttLib/tables/_v_m_t_x_test.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
from fontTools.ttLib.tables._v_m_t_x import table__v_m_t_x
import _h_m_t_x_test
import unittest
diff --git a/Tests/ttLib/tables/data/C_F_F__2.bin b/Tests/ttLib/tables/data/C_F_F__2.bin
index faa0e919..52b0177e 100644
--- a/Tests/ttLib/tables/data/C_F_F__2.bin
+++ b/Tests/ttLib/tables/data/C_F_F__2.bin
Binary files differ
diff --git a/Tests/ttLib/tables/data/C_F_F__2.ttx b/Tests/ttLib/tables/data/C_F_F__2.ttx
index c86252bb..95f840f6 100644
--- a/Tests/ttLib/tables/data/C_F_F__2.ttx
+++ b/Tests/ttLib/tables/data/C_F_F__2.ttx
@@ -21,7 +21,7 @@
<xMin value="51"/>
<yMin value="-115"/>
<xMax value="560"/>
- <yMax value="762"/>
+ <yMax value="731"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="3"/>
<fontDirectionHint value="2"/>
@@ -106,6 +106,8 @@
<blend value="190 -110 -162 0 -110 -162"/>
<blend value="200 0 -6 0 0 -6"/>
</StemSnapV>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
</Private>
</FontDict>
</FDArray>
diff --git a/Tests/ttLib/tables/data/NotoColorEmoji.subset.index_format_3.ttx b/Tests/ttLib/tables/data/NotoColorEmoji.subset.index_format_3.ttx
new file mode 100644
index 00000000..6a1728e8
--- /dev/null
+++ b/Tests/ttLib/tables/data/NotoColorEmoji.subset.index_format_3.ttx
@@ -0,0 +1,705 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.44">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="eight"/>
+ <GlyphID id="2" name="registered"/>
+ <GlyphID id="3" name="uni2049"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="2.011"/>
+ <checkSumAdjustment value="0x73631d70"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00001011"/>
+ <unitsPerEm value="2048"/>
+ <created value="Wed May 22 20:00:43 2013"/>
+ <modified value="Thu Jan 30 14:31:14 2020"/>
+ <xMin value="0"/>
+ <yMin value="-500"/>
+ <xMax value="2550"/>
+ <yMax value="1900"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="8"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1900"/>
+ <descent value="-500"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="2550"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="2550"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="4"/>
+ <maxPoints value="8"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="2"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="1"/>
+ <maxFunctionDefs value="1"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="64"/>
+ <maxSizeOfInstructions value="46"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="2550"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="1331"/>
+ <ySubscriptYSize value="1433"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="286"/>
+ <ySuperscriptXSize value="1331"/>
+ <ySuperscriptYSize value="1433"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="983"/>
+ <yStrikeoutSize value="102"/>
+ <yStrikeoutPosition value="530"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="6"/>
+ <bProportion value="9"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="GOOG"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="56"/>
+ <usLastCharIndex value="8265"/>
+ <sTypoAscender value="1900"/>
+ <sTypoDescender value="-500"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="1900"/>
+ <usWinDescent value="500"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="0"/>
+ <sCapHeight value="1900"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="1"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="2550" lsb="0"/>
+ <mtx name="eight" width="2550" lsb="0"/>
+ <mtx name="registered" width="2550" lsb="0"/>
+ <mtx name="uni2049" width="2550" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_14 platformID="0" platEncID="5">
+ <map uv="0x38" uvs="0xfe0f"/>
+ <map uv="0xae" uvs="0xfe0f"/>
+ <map uv="0x2049" uvs="0xfe0f"/>
+ </cmap_format_14>
+ <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="52" language="0" nGroups="3">
+ <map code="0x38" name="eight"/><!-- DIGIT EIGHT -->
+ <map code="0xae" name="registered"/><!-- REGISTERED SIGN -->
+ <map code="0x2049" name="uni2049"/><!-- EXCLAMATION QUESTION MARK -->
+ </cmap_format_12>
+ </cmap>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright 2013 Google Inc.
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Noto Color Emoji
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ Noto Color Emoji
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Noto Color Emoji
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 2.011;GOOG;noto-emoji:20180424; pistol
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ NotoColorEmoji
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-1244"/>
+ <underlineThickness value="131"/>
+ <isFixedPitch value="1"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CBDT>
+ <header version="3.0"/>
+ <strikedata index="0">
+ <cbdt_bitmap_format_17 name="registered">
+ <SmallGlyphMetrics>
+ <height value="128"/>
+ <width value="136"/>
+ <BearingX value="0"/>
+ <BearingY value="101"/>
+ <Advance value="136"/>
+ </SmallGlyphMetrics>
+ <rawimagedata>
+ 89504e47 0d0a1a0a 0000000d 49484452
+ 00000088 00000080 08030000 00e737d1
+ 0d000000 ff504c54 454c6971 74747475
+ 75757878 78777777 7b7b7b7e 7e7e6868
+ 68787878 79797977 77777777 77787878
+ 6969697b 7b7b7777 77737373 6f6f6f6c
+ 6c6c6767 67787878 6a6a6a64 64646161
+ 617d7d7d 7a7a7a60 60607575 75797979
+ 6666665e 5e5e7171 716e6e6e 6565655c
+ 5c5c5a5a 5a6c6c6c 58585857 57577373
+ 73555555 53535352 52526e6e 6e505050
+ 4e4e4e6b 6b6b4c4c 4c666666 4b4b4b66
+ 66666565 65494949 65656547 47474646
+ 465b5b5b 4444445a 5a5a4242 42606060
+ 5757573f 3f3f6060 60575757 3d3d3d58
+ 58583a3a 3a5c5c5c 5d5d5d39 39393737
+ 37353535 33333353 53535252 52525252
+ 2f2f2f52 52524f4f 4f2b2b2b 28282825
+ 25252323 23212121 f9766a48 00000050
+ 74524e53 00406180 bfefff20 9fcf508e
+ df30ffff ffffffff 10ffffff ffffffff
+ afffffff ffffffff 70ffffef ffffff80
+ ffffc0ff 8fffefa4 ffdfffff 50ff10ff
+ c162ff80 40ffa7ff d6efffff ffff8fdb
+ 7fffefbf 08ff47de 00000bbe 49444154
+ 7801ecd5 d77eab30 0c067033 dcb0629b
+ 8d11a381 aef77fc2 23b70c93 09745c9d
+ ff5533ac 7e96941f e4bfff7e 8d615ab6
+ 4d9f26d4 b62dd320 7fea603a eed30dae
+ 631ec89f f0fce0e9 81c0f7c8 2f3bfaf3
+ 30181761 144fa250 70360fca 3f925f93
+ 98d34078 9866f915 591af269 4866f23b
+ 312c5a7c 9222cdcb 3bf254c8 e213b592
+ 5f8bc144 5cae100b f63b5186 181c7bb1
+ 529ef221 0af93946 50281097 9bc45028
+ 81f15353 a976c5d0 a2543f32 1f8f1648
+ d6cd2eb5 2c10f5be df0ebf40 2cbaf63f
+ a210a098 0084d1b5 b4112b90 ffcda61c
+ dc0241d6 9c89435e 5cc5c38b 301914c8
+ 3d7c6b4b e9b576c4 2d2bee60 6d7cad29
+ d420bb99 0cf1653b f267c906 d4f62dcf
+ 1878966f 533690cf f9b2299c 2193ec64
+ 31d4968d e624d897 407fc8ea 8fe5807d
+ 11a74653 b60c5964 1787a168 39ed2185
+ 7fbcf75c 1cb240b6 1c0f7276 e788bb59
+ 13b24fce c3611bea 2c0a9b6e 16ef4ca2
+ 6ac9ac9b a592216a 252b1f4d 0cc9b49b
+ 65724f12 4b95c9bb 492ef418 eba308bd
+ 86dcbe27 e6598e5a 32541dc8 06878a21
+ 59eb49b6 fe760c89 b4b94412 05c6f687
+ a544913e 1db4a1cc 814a29e3 794b8544
+ 4e42364b 1c89c4bc b331bea4 87d5c75d
+ 29b5356b 409d36c9 2ea6ba13 cc49527c
+ e9aebd92 affad98f 4ad58fe0 88350379
+ 07b52bcb bb72d7a3 3a25ca7e a4a6ec93
+ 553c75b2 9b72f0af 3b24b65c 21f0bdab
+ fde55392 4eddcb5b 35187a71 cec65e3a
+ 7225ea1f ce0ada17 37a36b86 53a9455d
+ e4705487 d5a8b3fc b6380d85 1c386751
+ 9c4512b5 b0d5ba5f 6edb8f5a d50f822c
+ 2979d33f d0652948 85be9005 7b5974d5
+ 6f38e01c bad741ca 397793a1 54d4af91
+ 4752b193 e59e60a1 f475d001 e70179c0
+ c213f578 22e77822 19ef94f6 eb349144
+ f4b84812 60a9fc75 50e30beb d1a6722e
+ f4e4586f 631094c3 459223d5 3b2db070
+ f2b021e5 db20c417 e63865ce e3d7d5fa
+ 673cba4c 62e23be1 58b97cd4 92e45dfb
+ f609bfed 903d4150 346cd7cc c1774eda
+ 1ddf937b 0d0180e6 e3cbab00 08123dc8
+ db166acf fde59a00 88d7a178 0300d6bd
+ 86003c7f 0c520030 c8ad20fd 29d4a531
+ 0e74a9c5 2406d118 00908ed5 9f01eeb4
+ c4d41ad2 01404526 3640fd31 eb045c88
+ f0a8a607 009be82a 00e8b496 98e41657
+ 6bc83f52 ca43c759 1c8ac2c0 163205c9
+ 258e2524 c7941448 4fa6effc efff5c7b
+ b35c6106 dfb059f6 53733807 fbb3cd8c
+ 00e59814 c16b23c8 601987c1 236d89d7
+ 60db3992 87e00609 bca9dd81 e025b622
+ b95b23bf eef6b70e bf3f372a ba72acc0
+ 76e27d82 05a61ac6 4940f304 5f535537
+ 28bc435a 4474ae0d 897f7bee 9b4898c3
+ fb06152e 5081e653 40033589 b5a53b10
+ 5ac4a56e 99eb8659 f9f32b79 f48e6489
+ 4ba026c1 2363acc0 9681f1b4 2f52b7a0
+ 08f5b18b da61fb9b 9ec2b406 c302c68f
+ f4cd30c6 db29189b 043d91d9 90084e01
+ 2685ab49 f777834c 18b335c2 19a3efe6
+ 196c370d 57dbf0a7 08631d11 c5182932
+ 5d33265d 4d33d63b fdf07aea b8089cfa
+ 73401013 9d6111fa 54adab95 30e5f06e
+ 63ea82e1 798d1d0b a7e68b60 0880c816
+ 037fc755 5babf060 7ba698d6 904681cf
+ c45ab56b 28adb589 27a20745 1098bc74
+ 3d5f2481 a94b5c46 593ba1fe ad5aabb1
+ 31b3b67f 7bbfb729 4e714364 6d6dea7a
+ b06ad8bf 1b6b6798 6a6ba97f ae84ea08
+ 11ec1122 f4c113b7 0b4feb7d 030ca3c1
+ 05e47891 089ee132 b51f030b 6b332cac
+ a010fb0b a4840879 ae832231 3c5bb90d
+ 2f823e5b ce25e605 e7eb8010 d9b7d022
+ 6e197247 c89af302 73c9b93f cd81f319
+ e69af303 11cf3126 674022ce f9aead2d
+ e117b590 c67c060b f97996cd 8f0d26cb
+ 4e2345a0 a65c6d4e 2d74ca32 830bcdb3
+ cccfd759 563891ed 8027d540 1eb37616
+ ac4dfc6f c0891459 b6f67221 448d390c
+ 1f478924 b01bb16f 5b7bd08a 7c59981e
+ 0b350cff 4524f444 84e86c35 17624b79
+ c09b956b 95f033f6 5ae11d22 e7867122
+ d39300f4 d1a18438 04a4082e 3456e4dc
+ a285b874 2bd370f1 22aea467 c70a7e47
+ ff592486 679bd77f 38c230a1 445e5b36
+ 82022bc8 f540de02 5a042b3b fa44ba22
+ 2129e248 0541ba7f eda0f19b 1f10d950
+ 224aa91d e630f445 942a5f3b 94aac76c
+ 797ced17 0e012502 01767630 1c2be2d8
+ 7539bff6 a86092cb 74acc87b c32d91f7
+ fb291590 04b744b0 754b64ff d100c34f
+ 4264f971 2faf7305 4401c927 44d8db53
+ 2217a52a ccb552db ff23b2cf 490f64ab
+ 94c66205 d7e7e507 29575f0d a9942732
+ be8f4202 9724b8c1 49ca149b 2b290f94
+ 48817949 e41057f7 79ec2470 f82ba0c0
+ 994aa7ec 8b6c9d68 0d1b1a2f a2e1ed45
+ 709b8b94 b53bfaad 972fa4cc bf1b8eb0
+ a59810f9 be0b7839 1af08821 3f623597
+ d2570ea1 f04ece45 8bec2ac7 7b4f241c
+ 108920c7 e63b5d35 c6ecb191 1af3d217
+ 31a6fe76 bc42a5c3 a613cddc cb142fc6
+ a4d8dcc3 9b44e397 5baa32e6 ad9fe679
+ 5744e73f d9bb6809 3f07be91 37632a6c
+ d6c6fca2 54f3bcc0 c619e64a 86447650
+ 787b796a 8028d75f 3f242f71 708304ca
+ 672c1679 4e9ddddf 8456c792 eb2010f4
+ 4da77785 2a0e1c98 924b72ce 56708e9b
+ feff7b5e d72e3028 d9fddeee 4a9aeea6
+ 67c0612f 84280f7f 9042cc6a 4184181c
+ 3c8c1041 2f199486 8b53c1d5 06664248
+ cb2bc1db b71d673c 9f5a4e22 c4fa5510
+ 12e2180e 13d23957 53c1e53a d6422441
+ e0e80d67 0ecea91e 64d419a4 58a0cfd2
+ 574b0d79 fb3beb89 2373b72d 53d307de
+ 9b6523c8 d98383f0 e6f4b9bc e9dc9c25
+ efcc4157 f7bfeab6 b15629ae 8b4a1029
+ 8320b194 d5d95fa4 942bae8f 24134214
+ b04d83b4 59c79baf 948e55b2 930f327e
+ 11a48056 1d9840d0 9f7a0d1c f1b874bd
+ 4ab9e83a d1815902 5ad119a4 df6838c3
+ 12092a41 236bafe7 b84c3a28 29675daf
+ f160be79 6d243ba5 c6578fbe 52f5c95f
+ 94525b66 8c713b6b 1b486e17 58e1baf3
+ abc24d29 0ad6ba47 9520535e 26690629
+ ee4ae973 85a26a9b 1381d277 7552ead6
+ ebc21ee2 dc124b5c 5faa411e 1e1c8491
+ 419032e5 a0d149d1 185a69ed 735cefb9
+ d4d255e2 9c069596 765a4f9f 1e89d61c
+ c4e1a2b5 ce99b3c2 ed25ac9f 6038e056
+ 38660b8e 1097d6e8 6cb4be15 4190392f
+ 928641b8 0dadcdb5 42d25950 bea17cb6
+ b552571c 5abd92b0 a5e5fb20 8c0c8211
+ 931ea475 70cc96a8 ae8291f2 405e8c84
+ 5bdabb20 c6ac3e3c 5263dafa b918634a
+ 6695b8dd f9f307b3 f4f97620 3c126362
+ 67742663 eeffda82 0c10a443 4d4fa68d
+ 90e464df 1a50a2b3 2bc4c6f0 40da7184
+ 761bb674 2b7c904f 8f6a1046 06c18869
+ cfc4982f 7b40c261 6d0deb3b 01053d9c
+ 62ee9320 c8865718 358d7873 0ecc5b61
+ 6f7c8eb9 737d105c 7bef7082 6410b46e
+ ad2e4463 5e8088f6 adeae84e 147f78de
+ 98e862db a8999e7a 6f812529 779a8f94
+ febcf6f8 fb704f4b dcb4bd3d 5bdedcf1
+ 1e84c0d6 32f5f172 6bf90e05 9aea5f2b
+ 4976452f 8ae338b1 5f1256b8 feee92ef
+ 501c1f7e 799b04d7 51afd855 725cfb44
+ 7c525f21 838ee7f8 808e704e 7ee20a3a
+ 471bdd2b bc1f9c0f 42670fde 18dcfe6f
+ af5e7694 85a1008e 9f1d2b5e 83585342
+ 4c8cdc02 2975a2f4 4a19bff7 7f96ef94
+ 4174eee0 e8ace647 5c68da93 bf674306
+ b3481cb0 356747ea 1710828c 2e6c059f
+ 0a6d7421 2154789d 1ecd995f a784798a
+ 273c5c1a 235e1ee1 4b6c058c ab68f024
+ 832fafeb 2e1a28ce a0b2be43 4cc372fc
+ 8afb9d29 f07faa36 67228e10 2f60b182
+ 472816e6 acf6eb0c 603616a1 8399e411
+ 520c1662 2a42b999 1c22b468 4c851748
+ 6b267b12 211ec002 018f10d9 9b49eba7
+ 54b0885e ad56a475 1391ac90 d505cc54
+ 68bb4289 709396e0 0f1a16e2 bee4e8cc
+ f8385793 29657606 a99d9b46 1cfd000e
+ 8b71821a 77617232 e00cbec1 3819e4c6
+ 5d34c35d 801b4b6a 77456cc9 40c9103e
+ 154a4506 5be1aed4 b776204d 50eeaeb5
+ 2979a178 15c03b41 c5157991 b6ee5a4e
+ 90861b55 0425a2bf 664a4a46 b6933a63
+ 28c44fa6 6567c988 96a6bf26 12822ab8
+ 19b3ebf5 9aeefbd7 da92aebf 40cbb67f
+ 6defcf5b 063f10a8 35dab9fe 0d516fd7
+ 1fdad6a2 7fc3edd6 4805f023 85a4283e
+ f4ef8943 9da67492 a6f541f4 ef1d628a
+ 64013f95 598a36e6 f9266643 91cde00e
+ 0a4ebddc dc909153 8f17701f 4cc55e6e
+ 4e8b983c f61483fb d136f652 f17c9ae9
+ 59a4b167 35dc5531 a6248dfb 37836b92
+ 31a3807b c39464b0 6bcce9ab 88936976
+ c9e01119 5e51a964 541e4dff 51446f8e
+ 65325255 010f134a 9b9c6dcb a615ceeb
+ 1d126d53 26132b43 78b04caa cd3794cc
+ e0570415 ff346678 2dff2aa6 75d7d9cd
+ c4769dd6 0cfefc79 94ff183d 2a93948d
+ 64820000 00004945 4e44ae42 6082
+ </rawimagedata>
+ </cbdt_bitmap_format_17>
+ <cbdt_bitmap_format_17 name="eight">
+ <SmallGlyphMetrics>
+ <height value="128"/>
+ <width value="136"/>
+ <BearingX value="0"/>
+ <BearingY value="101"/>
+ <Advance value="136"/>
+ </SmallGlyphMetrics>
+ <rawimagedata>
+ 89504e47 0d0a1a0a 0000000d 49484452
+ 00000088 00000080 08030000 00e737d1
+ 0d000000 36504c54 454c6971 40404040
+ 40404040 40404040 40404071 7171c3c3
+ c3dfdfdf efefeff4 f4f4fafa fad0d0d0
+ f8f8f8b6 b6b6e7e7 e7909090 a6a6a6ad
+ 4143d100 00001274 524e5300 0a131e29
+ 3340739e c8dfff84 f266b34d 59e71538
+ bc000003 04494441 547801ed 9ac1b29d
+ 200c860b f02902a2 beffcb76 3a3d73ab
+ a15e4f17 c959946f ed8cbf49 0249cc8f
+ c1603018 0c0683c1 6030b0c7 f910232f
+ 620cde7d 44458874 c460adc5 456e88ce
+ 5ec634a7 25971779 49f3642c 25005097
+ d2b15400 828d8e08 5073f92b 6b028856
+ 3a5a2eb7 e406441b 1da97c4b 02a2858e
+ ad3cb04d ea4a82d0 71c3a21d b1eece2f
+ 920d70ba 8e99cb5b ccaacef1 30e5f216
+ eb045ed3 20a93b37 e61df639 ad7dea44
+ c50899d6 ee752fa6 d49bc4e9 a54cbdbe
+ ece0c47c 155921e8 79662967 1a10bdfb
+ 559944a4 caace71b a0f38b3f 994b0490
+ 9a6f1c34 190527e3 0719414d 2b6fbc30
+ fe266c1f c5a19bb4 822408db cfe085d0
+ 6623447c f10e4e9e ffe2c289 26490374
+ c1bc7e5e 887ce07f 102282f5 d9355659
+ b3839342 4cb2c6c3 fc2fe95b c1ab5dbe
+ 0f075a6f 311522e4 72e27ac4 7b71c467
+ c0a80c48 9c947820 c932c0aa 30aad732
+ e0d02b8c 04519864 9d3971ac 7aa5a2c0
+ 21a2a4a4 e9a654cc 80576d27 f6b59c59
+ b7b9c121 8be7f580 a8dc60b5 37db1a54
+ c7241ea8 e5910a78 fde6b7ad 0fcd55b3
+ 18d60460 5fca372c bba20ea1 847a6b94
+ 5cb1d1e1 7930c9b6 03380b1d d31b1323
+ bcbe8e23 9707f204 78751d6b 79643d74
+ bde3a48e 4f2989bd 8e9c5a83 36a72c94
+ 68cef37c 77e72d8d 2f5a36bb f5ba7e3f
+ 7161331a 1979d8bb 1b85f0bb 300a208b
+ 955dcb24 7db70fd1 5dfe586c b2b8b628
+ e2f3a576 160312cd 625196ce b5fbe0d8
+ 3f11f45b df1570bd cd54e612 cf438767
+ ad2a21b2 f79ded73 77ac3eca 9bc13f75
+ c74d5f88 7c897c66 083116f2 744878a8
+ e642b6e7 ac5113b2 cb6b5ebe 05c8327d
+ f57f09b4 ce24415e cf80c174 7301bcb0
+ 199b3ce2 2d7e1b55 e4e88a26 1f081675
+ d17afcd9 18713ed2 8d2cb42a 2360e99a
+ 6d313292 bed36a7a 5bdf5e22 4746fad3
+ 3c072cfd c6480338 eaf63287 c86f2d93
+ 1ce56d0e d5f126b5 bc49d26c f5bc3c29
+ eed980f0 a9ad0da1 23daefb1 98e9104a
+ ead330af 9aecf6bc 37cc8b46 c3bc762b
+ 6569669b 681e803d e5d291d3 0180375d
+ cd63afd7 15c1ba83 b239242e 708bf502
+ a70ff410 fca7166b f9e2338b b583c160
+ 30180c06 83c160f0 13e55b78 1b5bd5fd
+ 5a000000 0049454e 44ae4260 82
+ </rawimagedata>
+ </cbdt_bitmap_format_17>
+ <cbdt_bitmap_format_17 name="uni2049">
+ <SmallGlyphMetrics>
+ <height value="128"/>
+ <width value="136"/>
+ <BearingX value="0"/>
+ <BearingY value="101"/>
+ <Advance value="136"/>
+ </SmallGlyphMetrics>
+ <rawimagedata>
+ 89504e47 0d0a1a0a 0000000d 49484452
+ 00000088 00000080 08030000 00e737d1
+ 0d000001 3b504c54 454c6971 d73b3bd9
+ 4141da44 44d94141 d94242d5 3737d83f
+ 3fd84040 d94040d9 4141d941 41d53838
+ d13030d8 3f3fee4a 4af74d4d e34545eb
+ 4747ff50 50fe5050 fe4e4eda 4242fe4f
+ 4ffb4747 d94040d3 3434fd4c 4ce14040
+ f34747fc 4b4bd83e 3eeb4242 fb4949fa
+ 4545d639 39d02e2e dc3a3aef 3f3fd333
+ 33f94343 d43737f8 4141f740 40ed3939
+ e13434d3 3434cf2b 2bf63e3e f53c3cd2
+ 3131f439 39d02c2c cc2626d0 2d2df337
+ 37f23535 e83030cf 2b2bf132 32ce2929
+ dd2b2bcb 2222f030 30cb2323 d42525cd
+ 2626ef2d 2dc81d1d c31313cb 2222eb25
+ 25ee2929 c81b1bdb 2121c91f 1fec2525
+ ea2222eb 2424c71a 1ac10c0c c61919c0
+ 0a0ac00a 0ac20c0c c10c0cd1 0b0bda0c
+ 0cc00a0a e10c0cc1 0808be06 06df0909
+ cb0202de 0606d403 03bc0202 dc0303b9
+ 0000b800 00da0101 b80000b9 0000b800
+ 00b70000 fbff4a1d 00000068 74524e53
+ 0040cfff df583080 afbfef8f 10209fff
+ ffffffff ffffffff ffff70ff ffffffff
+ ffffffff 60ffffef ffffffff ffffffbf
+ ffffffff 9fd5ffff ffffffff ffff80ff
+ 8fffffff ef40ffff ffbfffff ffffffff
+ a0ff50df ff8fffff 8fffffff ffffffff
+ ffffdf9f ff50ff8f 9e461cce 00000655
+ 49444154 7801ec96 e9e2e22a 0c476ff7
+ 55812254 dcfabfef ff929320 636b6c71
+ c3d98fdf f0977808 a8fdef1f ff788128
+ 4e6e48b3 7b557956 94553d56 c4555344
+ ed3b1e59 328bcf64 95954e81 1237d9cb
+ 2269324b ba946fb3 2af151af f3d744a0
+ 96710283 c5d5bc46 719985e8 189b9430
+ 7171a9b2 1745b824 7058cc7d 1a827179
+ 0be79d70 2a513891 b96b9d3a 8b8d5c04
+ a78934ed c744dae6 ac31492b 3e833c1f
+ 529a7f48 64156b00 34b81f6c c6120d3c
+ 7953a0e2 56041649 2caf612d 614b6320
+ 6c843509 2f821ebe 7110140c c59a0417
+ b11e5dbf d928fe20 ea691314 d910a848
+ 6b3dfc16 0437933c ac486c3d 36923f07
+ 54a56d48 9102ef47 3f734b7b c63a21ec
+ 4f6ccf29 b2c79934 0f8ba45a b39e702d
+ 92a387ea a949dfe9 291da32e 4a8a670e
+ a79a1161 30d3ab84 e654830b 4d49ae55
+ 94b41baa de1419eb 236d1357 26dbef1a
+ 55d514f8 60a22d82 13153c9c 28944865
+ 8c564a49 b5e5eea5 943640dd 8c9fb1da
+ c506d05c f131266d 70fda888 319d2274
+ c65c4472 e3025272 47af5123 bb79d23b
+ 9b8c833b 77326d18 9106baef ad08771b
+ e5e851ce b45f1b9b e5e34014 87a52c8c
+ 486a8c50 167749e4 c14ce74d 4d84cd21
+ 0a398073 10113c99 e3dea2e0 fced9bcb
+ e75eda34 de26f4c6 224c3f2e b2274c44
+ 0a9cb643 49789da0 73bcd4ac ad8d1198
+ 430d2b82 67138510 29b1f3c5 449db798
+ 2f35436f c3210749 87366617 4224a56f
+ fbbf906d 4df3c298 e62191c6 18e111b1
+ 873ee1e8 ff1ae07d 3d2cf5f2 53cc8888
+ cbae231c 35e95bfb da655040 45d27744
+ 0a27320c 43779c70 1806ef06 5750703c
+ 4d38c2c2 6322c370 3811e0d3 5004a987
+ 1bfc77ef 5322d940 895b6fbf 7818bec2
+ 8ba0493c 4ca9d7c4 8352bd23 f23fc18a
+ bc088a4c 7bfd13f9 65446a10 d94ef87a
+ 46644b78 43a4c5af 2f11a9be 114717ba
+ 11c34010 40b74cee 89a10ca6 630ef9ff
+ 3fac7315 d99b8241 ca8d78a5 993c3951
+ cd0b340b 217cee93 6d8da29a 2752ca4f
+ 96372915 e5e54e4a cdb71eb3 21389d50
+ 5ecea57c f6a70cb6 2e8f0051 a81a7fea
+ 594a4b47 80dc48a9 c77eb494 a378c898
+ 251ba2d0 7cf7970c 9f1a0622 ec0f0f72
+ 4e9190c9 64c22138 6541ae51 34c183e0
+ f0383ce4 113d3df5 a327132b 12205396
+ 3c889a20 337fc7e0 70454343 2e2d6ac6
+ 9f99c984 0721c1ea 6910e698 07330b5c
+ 144567b9 5c9a5910 83934876 a034efcd
+ aca81492 e358042b eb094e97 89907590
+ 68087384 2b1aa72b 4a83ccc3 89394ea5
+ 8ed922f1 c7144098 6313e4b0 614532a4
+ 3f52ec40 2e694808 1cdbedb6 e7c04d51
+ 5a50e110 9cca1c5b e4913220 bb202910
+ f1ede07d 644f8342 4455e060 59fdb814
+ 5baeeb7a d104996f 71cb7060 8b4f2dea
+ ba8aebee f1cd2d73 d4dc5104 594555d5
+ c1d1963b 4a21f716 1f9db77e 921d0cd2
+ 068985a0 597741b3 2b70d0ca 390e712e
+ 06f2e878 b373c89e 068608eb 5cdd777c
+ 1143c74a 0ac25014 865359b1 2f604d6f
+ 2305eb05 f50a27c8 d1d57dff 9759ab4d
+ 120b329b ccf20fed c97c97ad 290639a4
+ 41f622d2 1ebcda4f 11a9cddf 2122dd21
+ a8135986 5412efd2 1d4521bd 881ca395
+ 9cccff43 9ad7ece8 d5cad22a e1b4f331
+ 2805b251 d53618a9 ea471e44 35829c55
+ fb8495fa 9b8bba51 06e4e297 04d9a976
+ fea8531d aa321057 0a442388 aaeecd0a
+ 90118075 8ba905b0 c9850031 045882d4
+ 0026af0e 684c01c8 14e420cb 2337d917
+ 825cdc97 00d94510 00f52a7f e40accde
+ c202d864 43c81842 2e411af2 176227db
+ 91340520 b8f9d904 08c9bb37 99c9abc9
+ 6d24790b 421ac4ba 8a416c10 c9716134
+ 909c5d24 4f45205f 410990fe 1137e643
+ 5eafdc83 129ead9a c8d19b22 90d977cc
+ 49f7d5cf abeb399a fcaae1f1 5e6556a8
+ 7f77f466 95b6c377 d090e9f8 698f0e76
+ 1c048130 8e73da5b 1f8b3b11 4b5b953a
+ 8222efff 043b241b 56d9c334 0e7be377
+ 2bc9ff8b 50869b3c b889a669 9a0b3aa5
+ 7ba455c7 6f18a4ee 332d990d c3bd3f51
+ 179abba8 40f58979 20437c09 aba13c7b
+ 645e3fcc 47abcf61 188e0dfe 7c0a2699
+ 361fafec 91563ba2 19c77138 36031e48
+ c1a30f9b 7955d76f 281ddec5 bc4e0c1e
+ 7564339d 900d49e1 e5a6025e 4f5d69b8
+ ff8c9d0a 16df996a de05aa21 e193ceef
+ c28c87b5 1b1200fc 1dc5c3af 0bcdbf7c
+ c88d6896 02e34388 d1ca0dc9 01d8a560
+ 015ced86 e401c6a5 300278aa 590b5443
+ da420876 3db178b4 d56e682e 0498d783
+ 194270f5 1b920c79 356f86ed 422305d3
+ 9e566d7e e3b4e9c9 26c6187e 9b803f77
+ c1e62302 3b230b11 f98f9b24 377c7b3c
+ d9990d83 74317392 df306cde 45e4fc56
+ af691a86 6f38d2f4 13a054ea b3000000
+ 0049454e 44ae4260 82
+ </rawimagedata>
+ </cbdt_bitmap_format_17>
+ </strikedata>
+ </CBDT>
+
+ <CBLC>
+ <header version="3.0"/>
+ <strike index="0">
+ <bitmapSizeTable>
+ <sbitLineMetrics direction="hori">
+ <ascender value="101"/>
+ <descender value="-27"/>
+ <widthMax value="136"/>
+ <caretSlopeNumerator value="0"/>
+ <caretSlopeDenominator value="0"/>
+ <caretOffset value="0"/>
+ <minOriginSB value="0"/>
+ <minAdvanceSB value="0"/>
+ <maxBeforeBL value="0"/>
+ <minAfterBL value="0"/>
+ <pad1 value="0"/>
+ <pad2 value="0"/>
+ </sbitLineMetrics>
+ <sbitLineMetrics direction="vert">
+ <ascender value="101"/>
+ <descender value="-27"/>
+ <widthMax value="136"/>
+ <caretSlopeNumerator value="0"/>
+ <caretSlopeDenominator value="0"/>
+ <caretOffset value="0"/>
+ <minOriginSB value="0"/>
+ <minAdvanceSB value="0"/>
+ <maxBeforeBL value="0"/>
+ <minAfterBL value="0"/>
+ <pad1 value="0"/>
+ <pad2 value="0"/>
+ </sbitLineMetrics>
+ <colorRef value="0"/>
+ <startGlyphIndex value="1"/>
+ <endGlyphIndex value="3"/>
+ <ppemX value="109"/>
+ <ppemY value="109"/>
+ <bitDepth value="32"/>
+ <flags value="1"/>
+ </bitmapSizeTable>
+ <!-- GlyphIds are written but not read. The firstGlyphIndex and
+ lastGlyphIndex values will be recalculated by the compiler. -->
+ <eblc_index_sub_table_3 imageFormat="17" firstGlyphIndex="1" lastGlyphIndex="2">
+ <glyphLoc id="1" name="eight"/>
+ <glyphLoc id="2" name="registered"/>
+ </eblc_index_sub_table_3>
+ <eblc_index_sub_table_3 imageFormat="17" firstGlyphIndex="3" lastGlyphIndex="3">
+ <glyphLoc id="3" name="uni2049"/>
+ </eblc_index_sub_table_3>
+ </strike>
+ </CBLC>
+
+ <vhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1275"/>
+ <descent value="-1275"/>
+ <lineGap value="0"/>
+ <advanceHeightMax value="2500"/>
+ <minTopSideBearing value="0"/>
+ <minBottomSideBearing value="0"/>
+ <yMaxExtent value="2400"/>
+ <caretSlopeRise value="0"/>
+ <caretSlopeRun value="1"/>
+ <caretOffset value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <reserved4 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfVMetrics value="1"/>
+ </vhea>
+
+ <vmtx>
+ <mtx name=".notdef" height="2500" tsb="0"/>
+ <mtx name="eight" height="2500" tsb="0"/>
+ <mtx name="registered" height="2500" tsb="0"/>
+ <mtx name="uni2049" height="2500" tsb="0"/>
+ </vmtx>
+
+</ttFont>
diff --git a/Tests/ttLib/tables/data/aots/classdef1_font1.ttx.GSUB b/Tests/ttLib/tables/data/aots/classdef1_font1.ttx.GSUB
index 21f1ab19..7a8f5109 100644
--- a/Tests/ttLib/tables/data/aots/classdef1_font1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/classdef1_font1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g3"/>
<Substitution in="g1" out="g4"/>
<Substitution in="g10" out="g13"/>
@@ -140,7 +140,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g4"/>
<Substitution in="g1" out="g5"/>
<Substitution in="g10" out="g14"/>
@@ -247,7 +247,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g5"/>
<Substitution in="g1" out="g6"/>
<Substitution in="g10" out="g15"/>
@@ -355,7 +355,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value=".notdef"/>
<Glyph value="g1"/>
<Glyph value="g2"/>
@@ -457,7 +457,7 @@
<Glyph value="g98"/>
<Glyph value="g99"/>
</Coverage>
- <ClassDef Format="1">
+ <ClassDef>
<ClassDef glyph="g18" class="1"/>
<ClassDef glyph="g19" class="1"/>
<ClassDef glyph="g20" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/classdef1_font2.ttx.GSUB b/Tests/ttLib/tables/data/aots/classdef1_font2.ttx.GSUB
index ec7278e2..563d6b67 100644
--- a/Tests/ttLib/tables/data/aots/classdef1_font2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/classdef1_font2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g3"/>
<Substitution in="g1" out="g4"/>
<Substitution in="g10" out="g13"/>
@@ -140,7 +140,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g4"/>
<Substitution in="g1" out="g5"/>
<Substitution in="g10" out="g14"/>
@@ -247,7 +247,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g5"/>
<Substitution in="g1" out="g6"/>
<Substitution in="g10" out="g15"/>
@@ -355,7 +355,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value=".notdef"/>
<Glyph value="g1"/>
<Glyph value="g2"/>
@@ -457,7 +457,7 @@
<Glyph value="g98"/>
<Glyph value="g99"/>
</Coverage>
- <ClassDef Format="1">
+ <ClassDef>
<ClassDef glyph="g18" class="2"/>
<ClassDef glyph="g19" class="2"/>
<ClassDef glyph="g20" class="2"/>
diff --git a/Tests/ttLib/tables/data/aots/classdef1_font3.ttx.GSUB b/Tests/ttLib/tables/data/aots/classdef1_font3.ttx.GSUB
index 9bc1e435..0bcbc728 100644
--- a/Tests/ttLib/tables/data/aots/classdef1_font3.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/classdef1_font3.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g3"/>
<Substitution in="g1" out="g4"/>
<Substitution in="g10" out="g13"/>
@@ -140,7 +140,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g4"/>
<Substitution in="g1" out="g5"/>
<Substitution in="g10" out="g14"/>
@@ -247,7 +247,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g5"/>
<Substitution in="g1" out="g6"/>
<Substitution in="g10" out="g15"/>
@@ -355,7 +355,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value=".notdef"/>
<Glyph value="g1"/>
<Glyph value="g2"/>
@@ -457,7 +457,7 @@
<Glyph value="g98"/>
<Glyph value="g99"/>
</Coverage>
- <ClassDef Format="1">
+ <ClassDef>
<ClassDef glyph="g18" class="2"/>
<ClassDef glyph="g19" class="2"/>
<ClassDef glyph="g20" class="2"/>
diff --git a/Tests/ttLib/tables/data/aots/classdef1_font4.ttx.GSUB b/Tests/ttLib/tables/data/aots/classdef1_font4.ttx.GSUB
index 681240da..dce27066 100644
--- a/Tests/ttLib/tables/data/aots/classdef1_font4.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/classdef1_font4.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g3"/>
<Substitution in="g1" out="g4"/>
<Substitution in="g10" out="g13"/>
@@ -140,7 +140,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g4"/>
<Substitution in="g1" out="g5"/>
<Substitution in="g10" out="g14"/>
@@ -247,7 +247,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g5"/>
<Substitution in="g1" out="g6"/>
<Substitution in="g10" out="g15"/>
@@ -355,7 +355,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value=".notdef"/>
<Glyph value="g1"/>
<Glyph value="g2"/>
@@ -457,7 +457,7 @@
<Glyph value="g98"/>
<Glyph value="g99"/>
</Coverage>
- <ClassDef Format="1">
+ <ClassDef>
</ClassDef>
<!-- SubClassSetCount=1 -->
<SubClassSet index="0" empty="1"/>
diff --git a/Tests/ttLib/tables/data/aots/classdef2_font1.ttx.GSUB b/Tests/ttLib/tables/data/aots/classdef2_font1.ttx.GSUB
index f602354d..7a8f5109 100644
--- a/Tests/ttLib/tables/data/aots/classdef2_font1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/classdef2_font1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g3"/>
<Substitution in="g1" out="g4"/>
<Substitution in="g10" out="g13"/>
@@ -140,7 +140,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g4"/>
<Substitution in="g1" out="g5"/>
<Substitution in="g10" out="g14"/>
@@ -247,7 +247,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g5"/>
<Substitution in="g1" out="g6"/>
<Substitution in="g10" out="g15"/>
@@ -355,7 +355,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value=".notdef"/>
<Glyph value="g1"/>
<Glyph value="g2"/>
@@ -457,7 +457,7 @@
<Glyph value="g98"/>
<Glyph value="g99"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g18" class="1"/>
<ClassDef glyph="g19" class="1"/>
<ClassDef glyph="g20" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/classdef2_font2.ttx.GSUB b/Tests/ttLib/tables/data/aots/classdef2_font2.ttx.GSUB
index d4650bd7..563d6b67 100644
--- a/Tests/ttLib/tables/data/aots/classdef2_font2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/classdef2_font2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g3"/>
<Substitution in="g1" out="g4"/>
<Substitution in="g10" out="g13"/>
@@ -140,7 +140,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g4"/>
<Substitution in="g1" out="g5"/>
<Substitution in="g10" out="g14"/>
@@ -247,7 +247,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g5"/>
<Substitution in="g1" out="g6"/>
<Substitution in="g10" out="g15"/>
@@ -355,7 +355,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value=".notdef"/>
<Glyph value="g1"/>
<Glyph value="g2"/>
@@ -457,7 +457,7 @@
<Glyph value="g98"/>
<Glyph value="g99"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g18" class="2"/>
<ClassDef glyph="g19" class="2"/>
<ClassDef glyph="g20" class="2"/>
diff --git a/Tests/ttLib/tables/data/aots/classdef2_font3.ttx.GSUB b/Tests/ttLib/tables/data/aots/classdef2_font3.ttx.GSUB
index 7cb045f8..0bcbc728 100644
--- a/Tests/ttLib/tables/data/aots/classdef2_font3.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/classdef2_font3.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g3"/>
<Substitution in="g1" out="g4"/>
<Substitution in="g10" out="g13"/>
@@ -140,7 +140,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g4"/>
<Substitution in="g1" out="g5"/>
<Substitution in="g10" out="g14"/>
@@ -247,7 +247,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g5"/>
<Substitution in="g1" out="g6"/>
<Substitution in="g10" out="g15"/>
@@ -355,7 +355,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value=".notdef"/>
<Glyph value="g1"/>
<Glyph value="g2"/>
@@ -457,7 +457,7 @@
<Glyph value="g98"/>
<Glyph value="g99"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g18" class="2"/>
<ClassDef glyph="g19" class="2"/>
<ClassDef glyph="g20" class="2"/>
diff --git a/Tests/ttLib/tables/data/aots/classdef2_font4.ttx.GSUB b/Tests/ttLib/tables/data/aots/classdef2_font4.ttx.GSUB
index 90f0b93f..dce27066 100644
--- a/Tests/ttLib/tables/data/aots/classdef2_font4.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/classdef2_font4.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g3"/>
<Substitution in="g1" out="g4"/>
<Substitution in="g10" out="g13"/>
@@ -140,7 +140,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g4"/>
<Substitution in="g1" out="g5"/>
<Substitution in="g10" out="g14"/>
@@ -247,7 +247,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in=".notdef" out="g5"/>
<Substitution in="g1" out="g6"/>
<Substitution in="g10" out="g15"/>
@@ -355,7 +355,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value=".notdef"/>
<Glyph value="g1"/>
<Glyph value="g2"/>
@@ -457,7 +457,7 @@
<Glyph value="g98"/>
<Glyph value="g99"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
</ClassDef>
<!-- SubClassSetCount=1 -->
<SubClassSet index="0" empty="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GDEF
index 971a3f19..08e65de0 100644
--- a/Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g18" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GPOS
index 93d3a052..3ab19a64 100644
--- a/Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos1_1_lookupflag_f1.ttx.GPOS
@@ -31,10 +31,10 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="1"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g20"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos1_1_simple_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos1_1_simple_f1.ttx.GPOS
index 00eacc3e..508d14e3 100644
--- a/Tests/ttLib/tables/data/aots/gpos1_1_simple_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos1_1_simple_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g20"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos1_1_simple_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos1_1_simple_f2.ttx.GPOS
index 1eff0218..ef78ecef 100644
--- a/Tests/ttLib/tables/data/aots/gpos1_1_simple_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos1_1_simple_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g20"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos1_1_simple_f3.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos1_1_simple_f3.ttx.GPOS
index c3850df4..523b139b 100644
--- a/Tests/ttLib/tables/data/aots/gpos1_1_simple_f3.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos1_1_simple_f3.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g20"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos1_1_simple_f4.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos1_1_simple_f4.ttx.GPOS
index f80286c2..027d687c 100644
--- a/Tests/ttLib/tables/data/aots/gpos1_1_simple_f4.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos1_1_simple_f4.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g20"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos1_2_font1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos1_2_font1.ttx.GPOS
index 11351a6f..058c302f 100644
--- a/Tests/ttLib/tables/data/aots/gpos1_2_font1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos1_2_font1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g20"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GDEF
index 971a3f19..08e65de0 100644
--- a/Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g18" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GPOS
index 88257acb..2b557f7e 100644
--- a/Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos1_2_font2.ttx.GPOS
@@ -31,10 +31,10 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="1"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g20"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_1_font6.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_1_font6.ttx.GPOS
index db1315b1..e27e72bb 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_1_font6.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_1_font6.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
</Coverage>
<ValueFormat1 value="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_1_font7.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_1_font7.ttx.GPOS
index 8b22294c..01f6b453 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_1_font7.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_1_font7.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g21"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GDEF
index 971a3f19..08e65de0 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g18" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GPOS
index 06b0691b..329315c8 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f1.ttx.GPOS
@@ -31,10 +31,10 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g19"/>
</Coverage>
<ValueFormat1 value="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GDEF
index 971a3f19..08e65de0 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g18" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GPOS
index 03e9f8bf..56506160 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_1_lookupflag_f2.ttx.GPOS
@@ -31,10 +31,10 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g19"/>
</Coverage>
<ValueFormat1 value="4"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f1.ttx.GPOS
index d8b4e83c..e5f8cc7c 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
</Coverage>
<ValueFormat1 value="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f2.ttx.GPOS
index cf71f475..820bea63 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_1_next_glyph_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
</Coverage>
<ValueFormat1 value="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_1_simple_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_1_simple_f1.ttx.GPOS
index 81b17204..55b84024 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_1_simple_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_1_simple_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
</Coverage>
<ValueFormat1 value="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_2_font1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_2_font1.ttx.GPOS
index 5595b999..d41d02dd 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_2_font1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_2_font1.ttx.GPOS
@@ -34,15 +34,15 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
</Coverage>
<ValueFormat1 value="1"/>
<ValueFormat2 value="2"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
<ClassDef glyph="g18" class="1"/>
</ClassDef1>
- <ClassDef2 Format="2">
+ <ClassDef2>
<ClassDef glyph="g19" class="1"/>
</ClassDef2>
<!-- Class1Count=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GDEF
index 971a3f19..08e65de0 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g18" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GPOS
index 7896be3d..c7f3f328 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_2_font2.ttx.GPOS
@@ -31,18 +31,18 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g19"/>
</Coverage>
<ValueFormat1 value="1"/>
<ValueFormat2 value="2"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
<ClassDef glyph="g19" class="1"/>
</ClassDef1>
- <ClassDef2 Format="2">
+ <ClassDef2>
<ClassDef glyph="g20" class="1"/>
</ClassDef2>
<!-- Class1Count=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GDEF
index 971a3f19..08e65de0 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g18" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GPOS
index 216a5914..25ac07ca 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_2_font3.ttx.GPOS
@@ -31,18 +31,18 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g19"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="2"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
<ClassDef glyph="g19" class="1"/>
</ClassDef1>
- <ClassDef2 Format="2">
+ <ClassDef2>
<ClassDef glyph="g20" class="1"/>
</ClassDef2>
<!-- Class1Count=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos2_2_font4.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_2_font4.ttx.GPOS
index a2e6017c..46f3e6ee 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_2_font4.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_2_font4.ttx.GPOS
@@ -34,15 +34,15 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
</Coverage>
<ValueFormat1 value="1"/>
<ValueFormat2 value="2"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
<ClassDef glyph="g18" class="1"/>
</ClassDef1>
- <ClassDef2 Format="2">
+ <ClassDef2>
<ClassDef glyph="g18" class="1"/>
</ClassDef2>
<!-- Class1Count=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos2_2_font5.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos2_2_font5.ttx.GPOS
index d2697352..1d589ca2 100644
--- a/Tests/ttLib/tables/data/aots/gpos2_2_font5.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos2_2_font5.ttx.GPOS
@@ -34,15 +34,15 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
</Coverage>
<ValueFormat1 value="1"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
<ClassDef glyph="g18" class="1"/>
</ClassDef1>
- <ClassDef2 Format="2">
+ <ClassDef2>
<ClassDef glyph="g18" class="1"/>
</ClassDef2>
<!-- Class1Count=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos3_font1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos3_font1.ttx.GPOS
index 8cbdbc7c..8babfbf4 100644
--- a/Tests/ttLib/tables/data/aots/gpos3_font1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos3_font1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<CursivePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g19"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GDEF
index b5ca1ed7..d2981b43 100644
--- a/Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g21" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GPOS
index 3ded3a7e..378af379 100644
--- a/Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos3_font2.ttx.GPOS
@@ -31,10 +31,10 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="3"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
<CursivePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g19"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GDEF
index b5ca1ed7..d2981b43 100644
--- a/Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g21" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GPOS
index e6a1c04a..7da5f5e8 100644
--- a/Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos3_font3.ttx.GPOS
@@ -31,10 +31,10 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="3"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
<CursivePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g19"/>
<Glyph value="g20"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GDEF
index 5118ad8d..e6b39464 100644
--- a/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GDEF
@@ -3,13 +3,13 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g17" class="1"/>
<ClassDef glyph="g18" class="1"/>
<ClassDef glyph="g19" class="3"/>
<ClassDef glyph="g20" class="3"/>
</GlyphClassDef>
- <MarkAttachClassDef Format="2">
+ <MarkAttachClassDef>
<ClassDef glyph="g19" class="1"/>
<ClassDef glyph="g20" class="2"/>
</MarkAttachClassDef>
diff --git a/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GPOS
index 744eaaa1..d7526b5e 100644
--- a/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f1.ttx.GPOS
@@ -31,13 +31,13 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="4"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="g19"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="g18"/>
</BaseCoverage>
<!-- ClassCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GDEF
index 2627f21d..8184af27 100644
--- a/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g17" class="1"/>
<ClassDef glyph="g18" class="1"/>
<ClassDef glyph="g19" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GPOS
index 78173362..3efed829 100644
--- a/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos4_lookupflag_f2.ttx.GPOS
@@ -31,13 +31,13 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="g19"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="g18"/>
</BaseCoverage>
<!-- ClassCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GDEF
index 4f9f64a6..a8df0fd3 100644
--- a/Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g17" class="1"/>
<ClassDef glyph="g18" class="1"/>
<ClassDef glyph="g19" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GPOS
index afe7e6a4..cfd3ddb1 100644
--- a/Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos4_multiple_anchors_1.ttx.GPOS
@@ -34,13 +34,13 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="g19"/>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="g17"/>
<Glyph value="g18"/>
</BaseCoverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GDEF
index 2627f21d..8184af27 100644
--- a/Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g17" class="1"/>
<ClassDef glyph="g18" class="1"/>
<ClassDef glyph="g19" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GPOS
index 3a2e6234..ccbc7844 100644
--- a/Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos4_simple_1.ttx.GPOS
@@ -34,10 +34,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="g19"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="g18"/>
</BaseCoverage>
<!-- ClassCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GDEF
index ff2dc980..461a9822 100644
--- a/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g17" class="1"/>
<ClassDef glyph="g18" class="2"/>
<ClassDef glyph="g19" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GPOS
index b5017b31..d5abadce 100644
--- a/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GPOS
@@ -34,10 +34,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkLigPos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="g19"/>
</MarkCoverage>
- <LigatureCoverage Format="1">
+ <LigatureCoverage>
<Glyph value="g18"/>
</LigatureCoverage>
<!-- ClassCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GSUB
index 85d33089..f81552a8 100644
--- a/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gpos5_font1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g30">
<Ligature components="g31" glyph="g18"/>
</LigatureSet>
diff --git a/Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GDEF
index 640f3ebf..f07a29b4 100644
--- a/Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g17" class="1"/>
<ClassDef glyph="g18" class="3"/>
<ClassDef glyph="g19" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GPOS
index d4c4da50..f2fd253d 100644
--- a/Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos6_font1.ttx.GPOS
@@ -34,10 +34,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkMarkPos index="0" Format="1">
- <Mark1Coverage Format="1">
+ <Mark1Coverage>
<Glyph value="g19"/>
</Mark1Coverage>
- <Mark2Coverage Format="1">
+ <Mark2Coverage>
<Glyph value="g18"/>
</Mark2Coverage>
<!-- ClassCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos7_1_font1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos7_1_font1.ttx.GPOS
index 3d82e684..db3b76ed 100644
--- a/Tests/ttLib/tables/data/aots/gpos7_1_font1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos7_1_font1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g19"/>
<Glyph value="g20"/>
@@ -51,7 +51,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos9_font1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos9_font1.ttx.GPOS
index bbc1c386..9bcef946 100644
--- a/Tests/ttLib/tables/data/aots/gpos9_font1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos9_font1.ttx.GPOS
@@ -36,7 +36,7 @@
<ExtensionPos index="0" Format="1">
<ExtensionLookupType value="1"/>
<SinglePos Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g20"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos9_font2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos9_font2.ttx.GPOS
index ac6d6afd..ffb993b2 100644
--- a/Tests/ttLib/tables/data/aots/gpos9_font2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos9_font2.ttx.GPOS
@@ -36,7 +36,7 @@
<ExtensionPos index="0" Format="1">
<ExtensionLookupType value="1"/>
<SinglePos Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g18"/>
<Glyph value="g20"/>
</Coverage>
@@ -47,7 +47,7 @@
<ExtensionPos index="1" Format="1">
<ExtensionLookupType value="1"/>
<SinglePos Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g19"/>
<Glyph value="g21"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GPOS
index ea85d9f6..a701d037 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainPosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GPOS
index bd176ba3..865a69bc 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainPosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GPOS
index b1890678..f36b48af 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f3.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainPosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GPOS
index f750652b..d497f587 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_boundary_f4.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g22"/>
</Coverage>
<!-- ChainPosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GPOS
index b07a9ba2..540a00e9 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_lookupflag_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -97,10 +97,10 @@
</Lookup>
<Lookup index="4">
<LookupType value="8"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g22"/>
</Coverage>
<!-- ChainPosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GPOS
index 09b5bd80..81374bce 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainPosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GPOS
index e8fb5162..404e9299 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_multiple_subrules_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainPosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GPOS
index 1f7539e5..87be738a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_next_glyph_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
<Glyph value="g23"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GPOS
index 1780fda6..c755f87b 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainPosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GPOS
index c2d3411b..e211d85f 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_simple_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g22"/>
</Coverage>
<!-- ChainPosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GPOS
index 3960d8af..a2da259e 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining1_successive_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- ChainPosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GPOS
index fe060771..f8b5eacc 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GPOS
index c5293714..aa832de6 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GPOS
index fa55cf10..6ee0bc91 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f3.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GPOS
index f943402a..1acf6c4e 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_boundary_f4.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GPOS
index b26d93b8..a5b4dec6 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_lookupflag_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -97,10 +97,10 @@
</Lookup>
<Lookup index="4">
<LookupType value="8"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GPOS
index 33399ba4..c5b52534 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GPOS
index dc10c2f5..b9512fad 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_multiple_subrules_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GPOS
index 9c18a9b0..b9e864df 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_next_glyph_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GPOS
index decc575e..3fddfb2c 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GPOS
index a35678da..248c52a8 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_simple_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GPOS
index 6775e5bd..49145520 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining2_successive_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -109,7 +109,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -118,7 +118,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -127,7 +127,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GPOS
index c3624274..4a4f076d 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -101,18 +101,18 @@
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=2 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g21"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g22"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g23"/>
</LookAheadCoverage>
<!-- PosCount=0 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GPOS
index 7b27f90c..4f38aec0 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -101,18 +101,18 @@
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=1 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g21"/>
</InputCoverage>
<!-- LookAheadGlyphCount=2 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g22"/>
</LookAheadCoverage>
- <LookAheadCoverage index="1" Format="1">
+ <LookAheadCoverage index="1">
<Glyph value="g23"/>
</LookAheadCoverage>
<!-- PosCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GPOS
index 73df34c1..4cde2283 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f3.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -102,14 +102,14 @@
<ChainContextPos index="0" Format="3">
<!-- BacktrackGlyphCount=0 -->
<!-- InputGlyphCount=1 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g21"/>
</InputCoverage>
<!-- LookAheadGlyphCount=2 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g22"/>
</LookAheadCoverage>
- <LookAheadCoverage index="1" Format="1">
+ <LookAheadCoverage index="1">
<Glyph value="g23"/>
</LookAheadCoverage>
<!-- PosCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GPOS
index 67bfc0e0..ab46ecbc 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_boundary_f4.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -101,14 +101,14 @@
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="3">
<!-- BacktrackGlyphCount=2 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g21"/>
</BacktrackCoverage>
- <BacktrackCoverage index="1" Format="1">
+ <BacktrackCoverage index="1">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=1 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g22"/>
</InputCoverage>
<!-- LookAheadGlyphCount=0 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GPOS
index b12b665f..2bc6f6b9 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_lookupflag_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -97,31 +97,31 @@
</Lookup>
<Lookup index="4">
<LookupType value="8"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="3">
<!-- BacktrackGlyphCount=2 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g21"/>
</BacktrackCoverage>
- <BacktrackCoverage index="1" Format="1">
+ <BacktrackCoverage index="1">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=3 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g22"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g23"/>
</InputCoverage>
- <InputCoverage index="2" Format="1">
+ <InputCoverage index="2">
<Glyph value="g24"/>
</InputCoverage>
<!-- LookAheadGlyphCount=2 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g25"/>
</LookAheadCoverage>
- <LookAheadCoverage index="1" Format="1">
+ <LookAheadCoverage index="1">
<Glyph value="g26"/>
</LookAheadCoverage>
<!-- PosCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GPOS
index d5b0c1fb..76be0caf 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_next_glyph_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -101,18 +101,18 @@
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g22"/>
</BacktrackCoverage>
<!-- InputGlyphCount=2 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g21"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g22"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g21"/>
</LookAheadCoverage>
<!-- PosCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GPOS
index b9b0ce5e..4a3d10a0 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -101,18 +101,18 @@
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=2 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g21"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g22"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g23"/>
</LookAheadCoverage>
<!-- PosCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GPOS
index 6d023ec4..1a1f57b6 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_simple_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -101,27 +101,27 @@
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="3">
<!-- BacktrackGlyphCount=2 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g21"/>
</BacktrackCoverage>
- <BacktrackCoverage index="1" Format="1">
+ <BacktrackCoverage index="1">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=3 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g22"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g23"/>
</InputCoverage>
- <InputCoverage index="2" Format="1">
+ <InputCoverage index="2">
<Glyph value="g24"/>
</InputCoverage>
<!-- LookAheadGlyphCount=2 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g25"/>
</LookAheadCoverage>
- <LookAheadCoverage index="1" Format="1">
+ <LookAheadCoverage index="1">
<Glyph value="g26"/>
</LookAheadCoverage>
<!-- PosCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GPOS
index f7c85b67..2c6ebb6e 100644
--- a/Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_chaining3_successive_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -101,24 +101,24 @@
<!-- SubTableCount=1 -->
<ChainContextPos index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g25"/>
</BacktrackCoverage>
<!-- InputGlyphCount=4 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g20"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g21"/>
</InputCoverage>
- <InputCoverage index="2" Format="1">
+ <InputCoverage index="2">
<Glyph value="g22"/>
</InputCoverage>
- <InputCoverage index="3" Format="1">
+ <InputCoverage index="3">
<Glyph value="g23"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g24"/>
</LookAheadCoverage>
<!-- PosCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GPOS
index 662ae54f..e61cc4ee 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GPOS
index 56a4f7b2..70c9250e 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_boundary_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GPOS
index 77d52eb0..ba6c1461 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_expansion_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GPOS
index 2d5f7963..8688a394 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -97,10 +97,10 @@
</Lookup>
<Lookup index="4">
<LookupType value="7"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GPOS
index ef419fff..8e2621fe 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_lookupflag_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -97,10 +97,10 @@
</Lookup>
<Lookup index="4">
<LookupType value="7"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GPOS
index 82750d50..03cec900 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GPOS
index 764703b5..652850fd 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_multiple_subrules_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GPOS
index ac00f86a..6ad207d0 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_next_glyph_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GPOS
index 031f56fd..a4c824fc 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_simple_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GPOS
index bb3d01d5..15faa222 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_simple_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GPOS
index 2d17ca51..11ae1c78 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context1_successive_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,7 +100,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GPOS
index b830138b..08af7f3b 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,10 +100,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
</ClassDef>
<!-- PosClassSetCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GPOS
index a48dc6a6..832e37bf 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_boundary_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,10 +100,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
</ClassDef>
<!-- PosClassSetCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GPOS
index 7573e488..b06382dd 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_classes_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,11 +100,11 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="1"/>
<ClassDef glyph="g22" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GPOS
index 4435c02a..a266ada4 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_classes_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,13 +100,13 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
<Glyph value="g24"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="1"/>
<ClassDef glyph="g22" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GPOS
index 584892fa..48833f75 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_expansion_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,10 +100,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GPOS
index 99546b55..d45c2804 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -97,13 +97,13 @@
</Lookup>
<Lookup index="4">
<LookupType value="7"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GPOS
index 4a0fcb80..6f3c016c 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_lookupflag_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -97,13 +97,13 @@
</Lookup>
<Lookup index="4">
<LookupType value="7"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GPOS
index d2f38c3b..1a9b1e2c 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,10 +100,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GPOS
index e5d0a630..f3f9d9d1 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_multiple_subrules_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,10 +100,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GPOS
index c1717812..655d1d3f 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_next_glyph_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,10 +100,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
</ClassDef>
<!-- PosClassSetCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GPOS
index 266f31b6..bc06db56 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_simple_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,10 +100,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GPOS
index 8001658f..c2964bbc 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_simple_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,10 +100,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
</ClassDef>
<!-- PosClassSetCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GPOS
index 2de586fc..797b37b1 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context2_successive_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -100,10 +100,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GPOS
index fd85d19a..f709b3b3 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -102,10 +102,10 @@
<ContextPos index="0" Format="3">
<!-- GlyphCount=2 -->
<!-- PosCount=0 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g20"/>
</Coverage>
</ContextPos>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GPOS
index bee96ff5..a885d1a6 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_boundary_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -102,7 +102,7 @@
<ContextPos index="0" Format="3">
<!-- GlyphCount=1 -->
<!-- PosCount=1 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
<PosLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GPOS
index e9c854c0..286a2000 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -97,18 +97,18 @@
</Lookup>
<Lookup index="4">
<LookupType value="7"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="3">
<!-- GlyphCount=3 -->
<!-- PosCount=3 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g21"/>
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
<Glyph value="g22"/>
</Coverage>
<PosLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GPOS
index e2b4a23e..ea697fc1 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_lookupflag_f2.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -97,18 +97,18 @@
</Lookup>
<Lookup index="4">
<LookupType value="7"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="3">
<!-- GlyphCount=3 -->
<!-- PosCount=1 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g21"/>
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
<Glyph value="g22"/>
</Coverage>
<PosLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GPOS
index 44ce0724..c4b9d8a6 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_next_glyph_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -102,10 +102,10 @@
<ContextPos index="0" Format="3">
<!-- GlyphCount=2 -->
<!-- PosCount=1 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g20"/>
</Coverage>
<PosLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GPOS
index c559a7df..7804fb8a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_simple_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -102,13 +102,13 @@
<ContextPos index="0" Format="3">
<!-- GlyphCount=3 -->
<!-- PosCount=3 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g21"/>
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
<Glyph value="g22"/>
</Coverage>
<PosLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GPOS b/Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GPOS
index 8ff7eea4..17b5ed27 100644
--- a/Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GPOS
+++ b/Tests/ttLib/tables/data/aots/gpos_context3_successive_f1.ttx.GPOS
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -55,7 +55,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -72,10 +72,10 @@
</Lookup>
<Lookup index="2">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<ValueFormat1 value="1"/>
@@ -102,16 +102,16 @@
<ContextPos index="0" Format="3">
<!-- GlyphCount=4 -->
<!-- PosCount=2 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g21"/>
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
<Glyph value="g22"/>
</Coverage>
- <Coverage index="3" Format="1">
+ <Coverage index="3">
<Glyph value="g23"/>
</Coverage>
<PosLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GDEF
index 971a3f19..08e65de0 100644
--- a/Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g18" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GSUB
index ba7b68fb..d5e0bf72 100644
--- a/Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub1_1_lookupflag_f1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="1"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g18" out="g23"/>
<Substitution in="g19" out="g24"/>
</SingleSubst>
diff --git a/Tests/ttLib/tables/data/aots/gsub1_1_modulo_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub1_1_modulo_f1.ttx.GSUB
index b9923da9..f145dd37 100644
--- a/Tests/ttLib/tables/data/aots/gsub1_1_modulo_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub1_1_modulo_f1.ttx.GSUB
@@ -34,11 +34,11 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=2 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g21" out="glyph32787"/>
<Substitution in="g22" out="glyph32788"/>
</SingleSubst>
- <SingleSubst index="1" Format="1">
+ <SingleSubst index="1">
<Substitution in="g19" out="glyph32789"/>
<Substitution in="g20" out="glyph32790"/>
</SingleSubst>
@@ -47,16 +47,16 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=4 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="glyph32787" out="g23"/>
</SingleSubst>
- <SingleSubst index="1" Format="1">
+ <SingleSubst index="1">
<Substitution in="glyph32788" out="g18"/>
</SingleSubst>
- <SingleSubst index="2" Format="1">
+ <SingleSubst index="2">
<Substitution in="glyph32789" out="g17"/>
</SingleSubst>
- <SingleSubst index="3" Format="1">
+ <SingleSubst index="3">
<Substitution in="glyph32790" out="g24"/>
</SingleSubst>
</Lookup>
diff --git a/Tests/ttLib/tables/data/aots/gsub1_1_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub1_1_simple_f1.ttx.GSUB
index e76ba74e..7de19b00 100644
--- a/Tests/ttLib/tables/data/aots/gsub1_1_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub1_1_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g18" out="g23"/>
<Substitution in="g19" out="g24"/>
</SingleSubst>
diff --git a/Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GDEF
index 971a3f19..08e65de0 100644
--- a/Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g18" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GSUB
index 51bfbb12..a518afbf 100644
--- a/Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub1_2_lookupflag_f1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="1"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="2">
+ <SingleSubst index="0">
<Substitution in="g18" out="g22"/>
<Substitution in="g20" out="g25"/>
</SingleSubst>
diff --git a/Tests/ttLib/tables/data/aots/gsub1_2_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub1_2_simple_f1.ttx.GSUB
index 55649d29..392ff3c4 100644
--- a/Tests/ttLib/tables/data/aots/gsub1_2_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub1_2_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="2">
+ <SingleSubst index="0">
<Substitution in="g18" out="g22"/>
<Substitution in="g20" out="g25"/>
</SingleSubst>
diff --git a/Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GDEF
index 971a3f19..08e65de0 100644
--- a/Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g18" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GSUB
index 2c597935..d3c37dca 100644
--- a/Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub2_1_lookupflag_f1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g18" out="g20,g21"/>
<Substitution in="g19" out="g22,g23"/>
</MultipleSubst>
diff --git a/Tests/ttLib/tables/data/aots/gsub2_1_multiple_sequences_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub2_1_multiple_sequences_f1.ttx.GSUB
index 5ec800e5..1c4cd4ca 100644
--- a/Tests/ttLib/tables/data/aots/gsub2_1_multiple_sequences_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub2_1_multiple_sequences_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g18" out="g20,g21"/>
<Substitution in="g19" out="g22,g23"/>
</MultipleSubst>
diff --git a/Tests/ttLib/tables/data/aots/gsub2_1_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub2_1_simple_f1.ttx.GSUB
index cae4f663..b2453ecd 100644
--- a/Tests/ttLib/tables/data/aots/gsub2_1_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub2_1_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g18" out="g20,g21,g22"/>
</MultipleSubst>
</Lookup>
diff --git a/Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GDEF
index 971a3f19..08e65de0 100644
--- a/Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g18" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GSUB
index 63c53b46..531c608b 100644
--- a/Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub3_1_lookupflag_f1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="3"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
- <AlternateSubst index="0" Format="1">
+ <AlternateSubst index="0">
<AlternateSet glyph="g18">
<Alternate glyph="g20"/>
<Alternate glyph="g21"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub3_1_multiple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub3_1_multiple_f1.ttx.GSUB
index 1eae117d..54a52192 100644
--- a/Tests/ttLib/tables/data/aots/gsub3_1_multiple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub3_1_multiple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="3"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <AlternateSubst index="0" Format="1">
+ <AlternateSubst index="0">
<AlternateSet glyph="g18">
<Alternate glyph="g20"/>
<Alternate glyph="g21"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub3_1_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub3_1_simple_f1.ttx.GSUB
index 7372cd05..9a516b27 100644
--- a/Tests/ttLib/tables/data/aots/gsub3_1_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub3_1_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="3"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <AlternateSubst index="0" Format="1">
+ <AlternateSubst index="0">
<AlternateSet glyph="g18">
<Alternate glyph="g20"/>
<Alternate glyph="g21"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GDEF
index ffcc7a16..269092d2 100644
--- a/Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g24" class="1"/>
</GlyphClassDef>
</GDEF>
diff --git a/Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GSUB
index 0982ef68..7dc3472e 100644
--- a/Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub4_1_lookupflag_f1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="4"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g18">
<Ligature components="g19,g20" glyph="g23"/>
</LigatureSet>
diff --git a/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f1.ttx.GSUB
index 9466f916..e9eb9eab 100644
--- a/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g18">
<Ligature components="g19,g20" glyph="g23"/>
<Ligature components="g19" glyph="g24"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f2.ttx.GSUB
index bc25e840..6756dfd1 100644
--- a/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligatures_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g18">
<Ligature components="g19" glyph="g24"/>
<Ligature components="g19,g20" glyph="g23"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligsets_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligsets_f1.ttx.GSUB
index 0ea6495c..ab043680 100644
--- a/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligsets_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub4_1_multiple_ligsets_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g18">
<Ligature components="g19" glyph="g23"/>
</LigatureSet>
diff --git a/Tests/ttLib/tables/data/aots/gsub4_1_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub4_1_simple_f1.ttx.GSUB
index ecc8fa68..7f3012aa 100644
--- a/Tests/ttLib/tables/data/aots/gsub4_1_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub4_1_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g18">
<Ligature components="g19,g20" glyph="g23"/>
</LigatureSet>
diff --git a/Tests/ttLib/tables/data/aots/gsub7_font1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub7_font1.ttx.GSUB
index 3f65d93a..cf43ec63 100644
--- a/Tests/ttLib/tables/data/aots/gsub7_font1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub7_font1.ttx.GSUB
@@ -35,7 +35,7 @@
<!-- SubTableCount=1 -->
<ExtensionSubst index="0" Format="1">
<ExtensionLookupType value="1"/>
- <SingleSubst Format="1">
+ <SingleSubst>
<Substitution in="g18" out="g23"/>
<Substitution in="g19" out="g24"/>
</SingleSubst>
diff --git a/Tests/ttLib/tables/data/aots/gsub7_font2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub7_font2.ttx.GSUB
index 98338ed5..1f488f31 100644
--- a/Tests/ttLib/tables/data/aots/gsub7_font2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub7_font2.ttx.GSUB
@@ -35,13 +35,13 @@
<!-- SubTableCount=2 -->
<ExtensionSubst index="0" Format="1">
<ExtensionLookupType value="1"/>
- <SingleSubst Format="1">
+ <SingleSubst>
<Substitution in="g18" out="g23"/>
</SingleSubst>
</ExtensionSubst>
<ExtensionSubst index="1" Format="1">
<ExtensionLookupType value="1"/>
- <SingleSubst Format="1">
+ <SingleSubst>
<Substitution in="g19" out="g29"/>
</SingleSubst>
</ExtensionSubst>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GSUB
index 49ed83d6..897946e9 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainSubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GSUB
index 7790bf97..896736c2 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainSubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GSUB
index c58e3d50..43a59147 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f3.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainSubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GSUB
index 43a1181c..d649b637 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_boundary_f4.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g22"/>
</Coverage>
<!-- ChainSubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GSUB
index e3bdf7fe..dd83bd7e 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_lookupflag_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,16 +70,16 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="6"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g22"/>
</Coverage>
<!-- ChainSubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GSUB
index a8ceb2b2..b446f626 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainSubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GSUB
index f73b4eca..9b6b835b 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_multiple_subrules_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainSubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GSUB
index 38581602..8b554abd 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_next_glyph_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
<Glyph value="g23"/>
</Coverage>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GSUB
index d7dbd6bb..f7dbe588 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g21"/>
</Coverage>
<!-- ChainSubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GSUB
index e97a6b10..a8894e53 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_simple_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g22"/>
</Coverage>
<!-- ChainSubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GSUB
index 146dd2a5..52cf963a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining1_successive_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- ChainSubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GSUB
index ecd0a6b5..9109e596 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GSUB
index 076cb30e..ead59dc9 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GSUB
index c6fc1dc1..8163db11 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f3.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GSUB
index 4221c6cc..48f94836 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_boundary_f4.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GSUB
index eaccba0a..a7f4ce6c 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_lookupflag_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,16 +70,16 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="6"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GSUB
index c0447c24..34cea465 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GSUB
index 6067dfd9..21cbe2a9 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_multiple_subrules_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GSUB
index f3dcb641..d14ac1b9 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_next_glyph_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GSUB
index 7997e3af..da6a915c 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GSUB
index bd3645a4..94f62173 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_simple_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GSUB
index fd6ead47..7dbc5e03 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining2_successive_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
@@ -88,7 +88,7 @@
<Glyph value="g25"/>
<Glyph value="g26"/>
</Coverage>
- <BacktrackClassDef Format="2">
+ <BacktrackClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -97,7 +97,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</BacktrackClassDef>
- <InputClassDef Format="2">
+ <InputClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
@@ -106,7 +106,7 @@
<ClassDef glyph="g25" class="25"/>
<ClassDef glyph="g26" class="26"/>
</InputClassDef>
- <LookAheadClassDef Format="2">
+ <LookAheadClassDef>
<ClassDef glyph="g20" class="20"/>
<ClassDef glyph="g21" class="21"/>
<ClassDef glyph="g22" class="22"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GSUB
index 579d6ce4..82eb1331 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -80,18 +80,18 @@
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=2 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g21"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g22"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g23"/>
</LookAheadCoverage>
<!-- SubstCount=0 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GSUB
index 5098ceb1..b5c16361 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -80,18 +80,18 @@
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=1 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g21"/>
</InputCoverage>
<!-- LookAheadGlyphCount=2 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g22"/>
</LookAheadCoverage>
- <LookAheadCoverage index="1" Format="1">
+ <LookAheadCoverage index="1">
<Glyph value="g23"/>
</LookAheadCoverage>
<!-- SubstCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GSUB
index 3d6b9666..76d5a462 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f3.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -81,14 +81,14 @@
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=0 -->
<!-- InputGlyphCount=1 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g21"/>
</InputCoverage>
<!-- LookAheadGlyphCount=2 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g22"/>
</LookAheadCoverage>
- <LookAheadCoverage index="1" Format="1">
+ <LookAheadCoverage index="1">
<Glyph value="g23"/>
</LookAheadCoverage>
<!-- SubstCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GSUB
index 9cf60092..9346d47b 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_boundary_f4.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -80,14 +80,14 @@
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=2 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g21"/>
</BacktrackCoverage>
- <BacktrackCoverage index="1" Format="1">
+ <BacktrackCoverage index="1">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=1 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g22"/>
</InputCoverage>
<!-- LookAheadGlyphCount=0 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GSUB
index 9cc951e6..c329936e 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_lookupflag_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,37 +70,37 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="6"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=2 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g21"/>
</BacktrackCoverage>
- <BacktrackCoverage index="1" Format="1">
+ <BacktrackCoverage index="1">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=3 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g22"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g23"/>
</InputCoverage>
- <InputCoverage index="2" Format="1">
+ <InputCoverage index="2">
<Glyph value="g24"/>
</InputCoverage>
<!-- LookAheadGlyphCount=2 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g25"/>
</LookAheadCoverage>
- <LookAheadCoverage index="1" Format="1">
+ <LookAheadCoverage index="1">
<Glyph value="g26"/>
</LookAheadCoverage>
<!-- SubstCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GSUB
index 00ff3578..46d1f859 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_next_glyph_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -80,18 +80,18 @@
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g22"/>
</BacktrackCoverage>
<!-- InputGlyphCount=2 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g21"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g22"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g21"/>
</LookAheadCoverage>
<!-- SubstCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GSUB
index 6cbf5f9d..722dcadb 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -80,18 +80,18 @@
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=2 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g21"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g22"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g23"/>
</LookAheadCoverage>
<!-- SubstCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GSUB
index 65fea5db..5842c554 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_simple_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -80,27 +80,27 @@
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=2 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g21"/>
</BacktrackCoverage>
- <BacktrackCoverage index="1" Format="1">
+ <BacktrackCoverage index="1">
<Glyph value="g20"/>
</BacktrackCoverage>
<!-- InputGlyphCount=3 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g22"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g23"/>
</InputCoverage>
- <InputCoverage index="2" Format="1">
+ <InputCoverage index="2">
<Glyph value="g24"/>
</InputCoverage>
<!-- LookAheadGlyphCount=2 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g25"/>
</LookAheadCoverage>
- <LookAheadCoverage index="1" Format="1">
+ <LookAheadCoverage index="1">
<Glyph value="g26"/>
</LookAheadCoverage>
<!-- SubstCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GSUB
index 6e8918fb..610ea565 100644
--- a/Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_chaining3_successive_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -80,24 +80,24 @@
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
- <BacktrackCoverage index="0" Format="1">
+ <BacktrackCoverage index="0">
<Glyph value="g25"/>
</BacktrackCoverage>
<!-- InputGlyphCount=4 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="g20"/>
</InputCoverage>
- <InputCoverage index="1" Format="1">
+ <InputCoverage index="1">
<Glyph value="g21"/>
</InputCoverage>
- <InputCoverage index="2" Format="1">
+ <InputCoverage index="2">
<Glyph value="g22"/>
</InputCoverage>
- <InputCoverage index="3" Format="1">
+ <InputCoverage index="3">
<Glyph value="g23"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="g24"/>
</LookAheadCoverage>
<!-- SubstCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GSUB
index 5ce2dbcb..6de65066 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GSUB
index f4df5df5..7d5772c3 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_boundary_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GSUB
index e03d5c34..e8dca8a8 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_expansion_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GSUB
index 7f7dbae5..579294fb 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,16 +70,16 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="5"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GSUB
index 921576c8..e583f6a1 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_lookupflag_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,16 +70,16 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="5"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GSUB
index 3bcb8829..22023208 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GSUB
index 04e9696d..df568c31 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_multiple_subrules_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GSUB
index 1276fb99..518e7549 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_next_glyph_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GSUB
index 4e135b5c..2906b061 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GSUB
index 787c216f..554168b9 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_simple_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GSUB
index e0b1c543..fc66abd5 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context1_successive_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,7 +79,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GSUB
index 705389b5..e1e12f8d 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,10 +79,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
</ClassDef>
<!-- SubClassSetCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GSUB
index 3a35d50d..d2598d95 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_boundary_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,10 +79,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
</ClassDef>
<!-- SubClassSetCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GSUB
index 67162033..394df78a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_classes_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,11 +79,11 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="1"/>
<ClassDef glyph="g22" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GSUB
index deeea523..4de5bc62 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_classes_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,13 +79,13 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
<Glyph value="g21"/>
<Glyph value="g22"/>
<Glyph value="g24"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="1"/>
<ClassDef glyph="g22" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GSUB
index 39528408..5ab7d08e 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_expansion_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,10 +79,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GSUB
index 80a62374..4a8d7493 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,19 +70,19 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="5"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GSUB
index 715a1cde..a672edf1 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_lookupflag_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,19 +70,19 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="5"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GSUB
index 827db065..07b5f5eb 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,10 +79,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GSUB
index 83d6af58..22527617 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_multiple_subrules_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,10 +79,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GSUB
index 09e9dc96..b78220bd 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_next_glyph_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,10 +79,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
</ClassDef>
<!-- SubClassSetCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GSUB
index 6bc8a468..35fff94c 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,10 +79,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GSUB
index f65bf4da..28ab11ee 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_simple_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,10 +79,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
</ClassDef>
<!-- SubClassSetCount=2 -->
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GSUB
index 3c7e0f9d..a3642077 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context2_successive_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -79,10 +79,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="g20"/>
</Coverage>
- <ClassDef Format="2">
+ <ClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="2"/>
<ClassDef glyph="g22" class="3"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GSUB
index 843e88d2..9a73fac0 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -81,10 +81,10 @@
<ContextSubst index="0" Format="3">
<!-- GlyphCount=2 -->
<!-- SubstCount=0 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g20"/>
</Coverage>
</ContextSubst>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GSUB
index cba8c311..d45e67b9 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_boundary_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -81,7 +81,7 @@
<ContextSubst index="0" Format="3">
<!-- GlyphCount=1 -->
<!-- SubstCount=1 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
<SubstLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GSUB
index d485a04e..90b53f45 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,24 +70,24 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="5"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="3">
<!-- GlyphCount=3 -->
<!-- SubstCount=3 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g21"/>
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
<Glyph value="g22"/>
</Coverage>
<SubstLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GSUB
index 016350f6..a82c3c0d 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_lookupflag_f2.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,24 +70,24 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="5"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="3">
<!-- GlyphCount=3 -->
<!-- SubstCount=1 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g21"/>
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
<Glyph value="g22"/>
</Coverage>
<SubstLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GSUB
index 10fcba54..a4f5addc 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_next_glyph_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -81,10 +81,10 @@
<ContextSubst index="0" Format="3">
<!-- GlyphCount=2 -->
<!-- SubstCount=1 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g20"/>
</Coverage>
<SubstLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GSUB
index 93f4bddf..15d6c087 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_simple_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -81,13 +81,13 @@
<ContextSubst index="0" Format="3">
<!-- GlyphCount=3 -->
<!-- SubstCount=3 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g21"/>
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
<Glyph value="g22"/>
</Coverage>
<SubstLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GDEF
index dba55502..b5c2ac3a 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g80" class="1"/>
<ClassDef glyph="g81" class="1"/>
<ClassDef glyph="g82" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GSUB
index 73e3567b..6558c692 100644
--- a/Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/gsub_context3_successive_f1.ttx.GSUB
@@ -33,7 +33,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
<Substitution in="g22" out="g62"/>
@@ -50,7 +50,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -58,9 +58,9 @@
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
@@ -70,7 +70,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
@@ -81,16 +81,16 @@
<ContextSubst index="0" Format="3">
<!-- GlyphCount=4 -->
<!-- SubstCount=2 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="g20"/>
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
<Glyph value="g21"/>
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
<Glyph value="g22"/>
</Coverage>
- <Coverage index="3" Format="1">
+ <Coverage index="3">
<Glyph value="g23"/>
</Coverage>
<SubstLookupRecord index="0">
diff --git a/Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GDEF
index 802351a4..736e2d8e 100644
--- a/Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g10" class="1"/>
<ClassDef glyph="g11" class="1"/>
<ClassDef glyph="g12" class="1"/>
@@ -21,7 +21,7 @@
<ClassDef glyph="g28" class="3"/>
<ClassDef glyph="g29" class="3"/>
</GlyphClassDef>
- <MarkAttachClassDef Format="2">
+ <MarkAttachClassDef>
<ClassDef glyph="g20" class="1"/>
<ClassDef glyph="g21" class="1"/>
<ClassDef glyph="g22" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GSUB
index f8064cbc..42876ead 100644
--- a/Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/lookupflag_ignore_attach_f1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="4"/>
- <LookupFlag value="512"/>
+ <LookupFlag value="512"/><!-- markAttachmentType[2] -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g11">
<Ligature components="g13,g26" glyph="g15"/>
</LigatureSet>
diff --git a/Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GDEF
index 2b0a6861..c6abc878 100644
--- a/Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g24" class="1"/>
<ClassDef glyph="g25" class="1"/>
</GlyphClassDef>
diff --git a/Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GSUB
index 0982ef68..7dc3472e 100644
--- a/Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/lookupflag_ignore_base_f1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="4"/>
- <LookupFlag value="2"/>
+ <LookupFlag value="2"/><!-- ignoreBaseGlyphs -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g18">
<Ligature components="g19,g20" glyph="g23"/>
</LigatureSet>
diff --git a/Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GDEF
index b67d75e7..e81490b5 100644
--- a/Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g24" class="1"/>
<ClassDef glyph="g25" class="1"/>
<ClassDef glyph="g26" class="1"/>
@@ -14,7 +14,7 @@
<ClassDef glyph="g31" class="3"/>
<ClassDef glyph="g32" class="3"/>
</GlyphClassDef>
- <MarkAttachClassDef Format="2">
+ <MarkAttachClassDef>
<ClassDef glyph="g25" class="1"/>
<ClassDef glyph="g26" class="2"/>
<ClassDef glyph="g28" class="1"/>
diff --git a/Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GSUB
index 95047b36..ffdc2aea 100644
--- a/Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/lookupflag_ignore_combination_f1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="4"/>
- <LookupFlag value="514"/>
+ <LookupFlag value="514"/><!-- ignoreBaseGlyphs markAttachmentType[2] -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g18">
<Ligature components="g19,g20" glyph="g23"/>
</LigatureSet>
diff --git a/Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GDEF
index 19aa7aa0..b9144314 100644
--- a/Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g24" class="1"/>
<ClassDef glyph="g25" class="1"/>
<ClassDef glyph="g26" class="2"/>
diff --git a/Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GSUB
index 3bc1e8d1..db7fcc0c 100644
--- a/Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/lookupflag_ignore_ligatures_f1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="4"/>
- <LookupFlag value="4"/>
+ <LookupFlag value="4"/><!-- ignoreLigatures -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g18">
<Ligature components="g19,g20" glyph="g23"/>
</LigatureSet>
diff --git a/Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GDEF b/Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GDEF
index 19aa7aa0..b9144314 100644
--- a/Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GDEF
+++ b/Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GDEF
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="g24" class="1"/>
<ClassDef glyph="g25" class="1"/>
<ClassDef glyph="g26" class="2"/>
diff --git a/Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GSUB b/Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GSUB
index 28b46826..b3fd500a 100644
--- a/Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GSUB
+++ b/Tests/ttLib/tables/data/aots/lookupflag_ignore_marks_f1.ttx.GSUB
@@ -31,9 +31,9 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="4"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="g18">
<Ligature components="g19,g20" glyph="g23"/>
</LigatureSet>
diff --git a/Tests/ttLib/tables/otBase_test.py b/Tests/ttLib/tables/otBase_test.py
index f1fa5b0d..ce0416e4 100644
--- a/Tests/ttLib/tables/otBase_test.py
+++ b/Tests/ttLib/tables/otBase_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import deHexStr
from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter
import unittest
diff --git a/Tests/ttLib/tables/otConverters_test.py b/Tests/ttLib/tables/otConverters_test.py
index 3b9d5e9f..1aff03bd 100644
--- a/Tests/ttLib/tables/otConverters_test.py
+++ b/Tests/ttLib/tables/otConverters_test.py
@@ -1,7 +1,3 @@
-# coding: utf-8
-from __future__ import print_function, division, absolute_import, \
- unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.loggingTools import CapturingLogHandler
from fontTools.misc.testTools import FakeFont, makeXMLWriter
from fontTools.misc.textTools import deHexStr
diff --git a/Tests/ttLib/tables/otTables_test.py b/Tests/ttLib/tables/otTables_test.py
index f39e27c2..9202aa55 100644
--- a/Tests/ttLib/tables/otTables_test.py
+++ b/Tests/ttLib/tables/otTables_test.py
@@ -1,11 +1,9 @@
-# coding: utf-8
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import getXML, parseXML, FakeFont
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter
import fontTools.ttLib.tables.otTables as otTables
+from io import StringIO
import unittest
@@ -170,7 +168,7 @@ class MultipleSubstTest(unittest.TestCase):
table = otTables.MultipleSubst()
table.Format = 1
for name, attrs, content in parseXML(
- '<Coverage Format="1">'
+ '<Coverage>'
' <Glyph value="o"/>'
' <Glyph value="l"/>'
'</Coverage>'
@@ -547,6 +545,37 @@ class InsertionMorphActionTest(unittest.TestCase):
})
+class SplitMultipleSubstTest:
+ def overflow(self, itemName, itemRecord):
+ from fontTools.otlLib.builder import buildMultipleSubstSubtable
+ from fontTools.ttLib.tables.otBase import OverflowErrorRecord
+
+ oldSubTable = buildMultipleSubstSubtable({'e': 1, 'a': 2, 'b': 3, 'c': 4, 'd': 5})
+ oldSubTable.Format = 1
+ newSubTable = otTables.MultipleSubst()
+
+ ok = otTables.splitMultipleSubst(oldSubTable, newSubTable, OverflowErrorRecord((None, None, None, itemName, itemRecord)))
+
+ assert ok
+ assert oldSubTable.Format == newSubTable.Format
+ return oldSubTable.mapping, newSubTable.mapping
+
+ def test_Coverage(self):
+ oldMapping, newMapping = self.overflow('Coverage', None)
+ assert oldMapping == {'a': 2, 'b': 3}
+ assert newMapping == {'c': 4, 'd': 5, 'e': 1}
+
+ def test_RangeRecord(self):
+ oldMapping, newMapping = self.overflow('RangeRecord', None)
+ assert oldMapping == {'a': 2, 'b': 3}
+ assert newMapping == {'c': 4, 'd': 5, 'e': 1}
+
+ def test_Sequence(self):
+ oldMapping, newMapping = self.overflow('Sequence', 4)
+ assert oldMapping == {'a': 2, 'b': 3,'c': 4}
+ assert newMapping == {'d': 5, 'e': 1}
+
+
def test_splitMarkBasePos():
from fontTools.otlLib.builder import buildAnchor, buildMarkBasePosSubtable
@@ -569,26 +598,92 @@ def test_splitMarkBasePos():
glyphMap = {g: i for i, g in enumerate(glyphOrder)}
oldSubTable = buildMarkBasePosSubtable(marks, bases, glyphMap)
- oldSubTable.MarkCoverage.Format = oldSubTable.BaseCoverage.Format = 1
newSubTable = otTables.MarkBasePos()
ok = otTables.splitMarkBasePos(oldSubTable, newSubTable, overflowRecord=None)
assert ok
- assert oldSubTable.Format == newSubTable.Format
- assert oldSubTable.MarkCoverage.glyphs == [
- "acutecomb", "gravecomb"
+
+ assert getXML(oldSubTable.toXML) == [
+ '<MarkBasePos Format="1">',
+ ' <MarkCoverage>',
+ ' <Glyph value="acutecomb"/>',
+ ' <Glyph value="gravecomb"/>',
+ ' </MarkCoverage>',
+ ' <BaseCoverage>',
+ ' <Glyph value="a"/>',
+ ' <Glyph value="c"/>',
+ ' </BaseCoverage>',
+ ' <!-- ClassCount=1 -->',
+ ' <MarkArray>',
+ ' <!-- MarkCount=2 -->',
+ ' <MarkRecord index="0">',
+ ' <Class value="0"/>',
+ ' <MarkAnchor Format="1">',
+ ' <XCoordinate value="0"/>',
+ ' <YCoordinate value="600"/>',
+ ' </MarkAnchor>',
+ ' </MarkRecord>',
+ ' <MarkRecord index="1">',
+ ' <Class value="0"/>',
+ ' <MarkAnchor Format="1">',
+ ' <XCoordinate value="0"/>',
+ ' <YCoordinate value="590"/>',
+ ' </MarkAnchor>',
+ ' </MarkRecord>',
+ ' </MarkArray>',
+ ' <BaseArray>',
+ ' <!-- BaseCount=2 -->',
+ ' <BaseRecord index="0">',
+ ' <BaseAnchor index="0" Format="1">',
+ ' <XCoordinate value="350"/>',
+ ' <YCoordinate value="500"/>',
+ ' </BaseAnchor>',
+ ' </BaseRecord>',
+ ' <BaseRecord index="1">',
+ ' <BaseAnchor index="0" Format="1">',
+ ' <XCoordinate value="300"/>',
+ ' <YCoordinate value="700"/>',
+ ' </BaseAnchor>',
+ ' </BaseRecord>',
+ ' </BaseArray>',
+ '</MarkBasePos>',
+ ]
+
+ assert getXML(newSubTable.toXML) == [
+ '<MarkBasePos Format="1">',
+ ' <MarkCoverage>',
+ ' <Glyph value="cedillacomb"/>',
+ ' </MarkCoverage>',
+ ' <BaseCoverage>',
+ ' <Glyph value="a"/>',
+ ' <Glyph value="c"/>',
+ ' </BaseCoverage>',
+ ' <!-- ClassCount=1 -->',
+ ' <MarkArray>',
+ ' <!-- MarkCount=1 -->',
+ ' <MarkRecord index="0">',
+ ' <Class value="0"/>',
+ ' <MarkAnchor Format="1">',
+ ' <XCoordinate value="0"/>',
+ ' <YCoordinate value="0"/>',
+ ' </MarkAnchor>',
+ ' </MarkRecord>',
+ ' </MarkArray>',
+ ' <BaseArray>',
+ ' <!-- BaseCount=2 -->',
+ ' <BaseRecord index="0">',
+ ' <BaseAnchor index="0" empty="1"/>',
+ ' </BaseRecord>',
+ ' <BaseRecord index="1">',
+ ' <BaseAnchor index="0" Format="1">',
+ ' <XCoordinate value="300"/>',
+ ' <YCoordinate value="0"/>',
+ ' </BaseAnchor>',
+ ' </BaseRecord>',
+ ' </BaseArray>',
+ '</MarkBasePos>',
]
- assert newSubTable.MarkCoverage.glyphs == ["cedillacomb"]
- assert newSubTable.MarkCoverage.Format == 1
- assert oldSubTable.BaseCoverage.glyphs == newSubTable.BaseCoverage.glyphs
- assert newSubTable.BaseCoverage.Format == 1
- assert oldSubTable.ClassCount == newSubTable.ClassCount == 1
- assert oldSubTable.MarkArray.MarkCount == 2
- assert newSubTable.MarkArray.MarkCount == 1
- assert oldSubTable.BaseArray.BaseCount == newSubTable.BaseArray.BaseCount
- assert newSubTable.BaseArray.BaseRecord[0].BaseAnchor[0] is None
- assert newSubTable.BaseArray.BaseRecord[1].BaseAnchor[0] == buildAnchor(300, 0)
if __name__ == "__main__":
diff --git a/Tests/ttLib/tables/tables_test.py b/Tests/ttLib/tables/tables_test.py
index 9d03814e..f66323fb 100644
--- a/Tests/ttLib/tables/tables_test.py
+++ b/Tests/ttLib/tables/tables_test.py
@@ -1,7 +1,5 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont, tagToXML
+from io import StringIO
import os
import sys
import re
@@ -255,13 +253,13 @@ def read_expected_ttx(testfile, tableTag):
def dump_ttx(font, tableTag):
- f = UnicodeIO()
+ f = StringIO()
font.saveXML(f, newlinestr='\n', tables=[tableTag])
return ttLibVersion_RE.sub('', f.getvalue())
def load_ttx(ttx):
- f = UnicodeIO()
+ f = StringIO()
f.write(ttx)
f.seek(0)
font = TTFont()
diff --git a/Tests/ttLib/tables/ttProgram_test.py b/Tests/ttLib/tables/ttProgram_test.py
index 9a7d232c..be6e86a0 100644
--- a/Tests/ttLib/tables/ttProgram_test.py
+++ b/Tests/ttLib/tables/ttProgram_test.py
@@ -1,11 +1,9 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib.tables.ttProgram import Program
from fontTools.misc.textTools import deHexStr
import array
+from io import StringIO
import os
-import re
import unittest
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
@@ -105,7 +103,7 @@ class ProgramTest(unittest.TestCase):
p = Program()
p.fromBytecode(BYTECODE)
ttfont = TestFont()
- buf = UnicodeIO()
+ buf = StringIO()
writer = XMLWriter(buf, newlinestr='\n')
try:
p.toXML(writer, ttfont)
diff --git a/Tests/ttLib/ttFont_test.py b/Tests/ttLib/ttFont_test.py
new file mode 100644
index 00000000..47cedeb7
--- /dev/null
+++ b/Tests/ttLib/ttFont_test.py
@@ -0,0 +1,48 @@
+import io
+from fontTools.ttLib import TTFont, newTable, registerCustomTableClass, unregisterCustomTableClass
+from fontTools.ttLib.tables.DefaultTable import DefaultTable
+
+
+class CustomTableClass(DefaultTable):
+
+ def decompile(self, data, ttFont):
+ self.numbers = list(data)
+
+ def compile(self, ttFont):
+ return bytes(self.numbers)
+
+ # not testing XML read/write
+
+
+table_C_U_S_T_ = CustomTableClass # alias for testing
+
+
+TABLETAG = "CUST"
+
+
+def test_registerCustomTableClass():
+ font = TTFont()
+ font[TABLETAG] = newTable(TABLETAG)
+ font[TABLETAG].data = b"\x00\x01\xff"
+ f = io.BytesIO()
+ font.save(f)
+ f.seek(0)
+ assert font[TABLETAG].data == b"\x00\x01\xff"
+ registerCustomTableClass(TABLETAG, "ttFont_test", "CustomTableClass")
+ try:
+ font = TTFont(f)
+ assert font[TABLETAG].numbers == [0, 1, 255]
+ assert font[TABLETAG].compile(font) == b"\x00\x01\xff"
+ finally:
+ unregisterCustomTableClass(TABLETAG)
+
+
+def test_registerCustomTableClassStandardName():
+ registerCustomTableClass(TABLETAG, "ttFont_test")
+ try:
+ font = TTFont()
+ font[TABLETAG] = newTable(TABLETAG)
+ font[TABLETAG].numbers = [4, 5, 6]
+ assert font[TABLETAG].compile(font) == b"\x04\x05\x06"
+ finally:
+ unregisterCustomTableClass(TABLETAG)
diff --git a/Tests/ttLib/woff2_test.py b/Tests/ttLib/woff2_test.py
index 4a474aed..c0d60cee 100644
--- a/Tests/ttLib/woff2_test.py
+++ b/Tests/ttLib/woff2_test.py
@@ -1,7 +1,7 @@
-from __future__ import print_function, division, absolute_import, unicode_literals
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, bytechr, byteord
from fontTools import ttLib
from fontTools.ttLib import woff2
+from fontTools.ttLib.tables import _g_l_y_f
from fontTools.ttLib.woff2 import (
WOFF2Reader, woff2DirectorySize, woff2DirectoryFormat,
woff2FlagsSize, woff2UnknownTagSize, woff2Base128MaxSize, WOFF2DirectoryEntry,
@@ -12,6 +12,7 @@ import unittest
from fontTools.misc import sstruct
from fontTools import fontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen
+from io import BytesIO
import struct
import os
import random
@@ -22,7 +23,10 @@ import pytest
haveBrotli = False
try:
- import brotli
+ try:
+ import brotlicffi as brotli
+ except ImportError:
+ import brotli
haveBrotli = True
except ImportError:
pass
@@ -201,7 +205,7 @@ def normalise_font(font, padding=4):
# drop DSIG but keep a copy
DSIG_copy = copy.deepcopy(font['DSIG'])
del font['DSIG']
- # ovverride TTFont attributes
+ # override TTFont attributes
origFlavor = font.flavor
origRecalcBBoxes = font.recalcBBoxes
origRecalcTimestamp = font.recalcTimestamp
@@ -1200,6 +1204,38 @@ class WOFF2RoundtripTest(object):
assert tmp.getvalue() == tmp2.getvalue()
assert ttFont2.reader.flavorData.transformedTables == {"hmtx"}
+ def test_roundtrip_no_glyf_and_loca_tables(self):
+ ttx = os.path.join(
+ os.path.dirname(current_dir), "subset", "data", "google_color.ttx"
+ )
+ ttFont = ttLib.TTFont()
+ ttFont.importXML(ttx)
+
+ assert "glyf" not in ttFont
+ assert "loca" not in ttFont
+
+ ttFont.flavor = "woff2"
+ tmp = BytesIO()
+ ttFont.save(tmp)
+
+ tmp2, ttFont2 = self.roundtrip(tmp)
+ assert tmp.getvalue() == tmp2.getvalue()
+ assert ttFont.flavor == "woff2"
+
+ def test_roundtrip_off_curve_despite_overlap_bit(self):
+ ttx = os.path.join(data_dir, "woff2_overlap_offcurve_in.ttx")
+ ttFont = ttLib.TTFont()
+ ttFont.importXML(ttx)
+
+ assert ttFont["glyf"]["A"].flags[0] == _g_l_y_f.flagOverlapSimple
+
+ ttFont.flavor = "woff2"
+ tmp = BytesIO()
+ ttFont.save(tmp)
+
+ _, ttFont2 = self.roundtrip(tmp)
+ assert ttFont2.flavor == "woff2"
+ assert ttFont2["glyf"]["A"].flags[0] == 0
class MainTest(object):
diff --git a/Tests/ttx/ttx_test.py b/Tests/ttx/ttx_test.py
index 97307fb7..c2463474 100644
--- a/Tests/ttx/ttx_test.py
+++ b/Tests/ttx/ttx_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.misc.testTools import parseXML
from fontTools.misc.timeTools import timestampSinceEpoch
from fontTools.ttLib import TTFont, TTLibError
@@ -19,7 +17,10 @@ try:
except ImportError:
zopfli = None
try:
- import brotli
+ try:
+ import brotlicffi as brotli
+ except ImportError:
+ import brotli
except ImportError:
brotli = None
diff --git a/Tests/ufoLib/GLIF1_test.py b/Tests/ufoLib/GLIF1_test.py
index 707c2097..85fcc71f 100644
--- a/Tests/ufoLib/GLIF1_test.py
+++ b/Tests/ufoLib/GLIF1_test.py
@@ -1,14 +1,8 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
import unittest
from fontTools.ufoLib.glifLib import GlifLibError, readGlyphFromString, writeGlyphToString
from .testSupport import Glyph, stripText
from itertools import islice
-try:
- basestring
-except NameError:
- basestring = str
# ----------
# Test Cases
# ----------
@@ -16,11 +10,11 @@ except NameError:
class TestGLIF1(unittest.TestCase):
def assertEqual(self, first, second, msg=None):
- if isinstance(first, basestring):
+ if isinstance(first, str):
first = stripText(first)
- if isinstance(second, basestring):
+ if isinstance(second, str):
second = stripText(second)
- return super(TestGLIF1, self).assertEqual(first, second, msg=msg)
+ return super().assertEqual(first, second, msg=msg)
def pyToGLIF(self, py):
py = stripText(py)
diff --git a/Tests/ufoLib/GLIF2_test.py b/Tests/ufoLib/GLIF2_test.py
index 2daa4533..ab9495db 100644
--- a/Tests/ufoLib/GLIF2_test.py
+++ b/Tests/ufoLib/GLIF2_test.py
@@ -1,14 +1,8 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
import unittest
from fontTools.ufoLib.glifLib import GlifLibError, readGlyphFromString, writeGlyphToString
from .testSupport import Glyph, stripText
from itertools import islice
-try:
- basestring
-except NameError:
- basestring = str
# ----------
# Test Cases
# ----------
@@ -16,11 +10,11 @@ except NameError:
class TestGLIF2(unittest.TestCase):
def assertEqual(self, first, second, msg=None):
- if isinstance(first, basestring):
+ if isinstance(first, str):
first = stripText(first)
- if isinstance(second, basestring):
+ if isinstance(second, str):
second = stripText(second)
- return super(TestGLIF2, self).assertEqual(first, second, msg=msg)
+ return super().assertEqual(first, second, msg=msg)
def pyToGLIF(self, py):
py = stripText(py)
diff --git a/Tests/ufoLib/UFO1_test.py b/Tests/ufoLib/UFO1_test.py
index 6194270d..5feb045a 100644
--- a/Tests/ufoLib/UFO1_test.py
+++ b/Tests/ufoLib/UFO1_test.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
import os
import shutil
import unittest
@@ -10,7 +8,7 @@ from fontTools.ufoLib import plistlib
from .testSupport import fontInfoVersion1, fontInfoVersion2
-class TestInfoObject(object): pass
+class TestInfoObject: pass
class ReadFontInfoVersion1TestCase(unittest.TestCase):
diff --git a/Tests/ufoLib/UFO2_test.py b/Tests/ufoLib/UFO2_test.py
index 04309ba7..68b4bafd 100644
--- a/Tests/ufoLib/UFO2_test.py
+++ b/Tests/ufoLib/UFO2_test.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
import os
import shutil
import unittest
@@ -10,7 +8,7 @@ from fontTools.ufoLib import plistlib
from .testSupport import fontInfoVersion2
-class TestInfoObject(object): pass
+class TestInfoObject: pass
class ReadFontInfoVersion2TestCase(unittest.TestCase):
diff --git a/Tests/ufoLib/UFO3_test.py b/Tests/ufoLib/UFO3_test.py
index 3cfd7c8f..c4218023 100644
--- a/Tests/ufoLib/UFO3_test.py
+++ b/Tests/ufoLib/UFO3_test.py
@@ -1,18 +1,15 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
import os
import shutil
import unittest
import tempfile
from io import open
-from fontTools.misc.py23 import unicode
from fontTools.ufoLib import UFOReader, UFOWriter, UFOLibError
from fontTools.ufoLib.glifLib import GlifLibError
from fontTools.misc import plistlib
from .testSupport import fontInfoVersion3
-class TestInfoObject(object): pass
+class TestInfoObject: pass
# --------------
@@ -3943,6 +3940,26 @@ class UFO3WriteLayersTestCase(unittest.TestCase):
result = list(writer.getGlyphSet("layer 2", defaultLayer=False).keys())
self.assertEqual(expected, result)
+ def testGetGlyphSetNoContents(self):
+ self.makeUFO()
+ os.remove(os.path.join(self.ufoPath, "glyphs.layer 1", "contents.plist"))
+
+ reader = UFOReader(self.ufoPath, validate=True)
+ with self.assertRaises(GlifLibError):
+ reader.getGlyphSet("layer 1")
+
+ writer = UFOWriter(self.ufoPath, validate=True)
+ with self.assertRaises(GlifLibError):
+ writer.getGlyphSet("layer 1", defaultLayer=False, expectContentsFile=True)
+
+ # There's a separate code path for < v3 UFOs.
+ with open(os.path.join(self.ufoPath, "metainfo.plist"), "wb") as f:
+ plistlib.dump(dict(creator="test", formatVersion=2), f)
+ os.remove(os.path.join(self.ufoPath, "glyphs", "contents.plist"))
+ writer = UFOWriter(self.ufoPath, validate=True, formatVersion=2)
+ with self.assertRaises(GlifLibError):
+ writer.getGlyphSet(expectContentsFile=True)
+
# make a new font with two layers
def testNewFontOneLayer(self):
@@ -4108,16 +4125,14 @@ class UFO3WriteLayersTestCase(unittest.TestCase):
self.makeUFO()
writer = UFOWriter(self.ufoPath)
writer.deleteGlyphSet("public.default")
+ writer.writeLayerContents(["layer 1", "layer 2"])
# directories
path = os.path.join(self.ufoPath, "glyphs")
- exists = os.path.exists(path)
- self.assertEqual(False, exists)
+ self.assertEqual(False, os.path.exists(path))
path = os.path.join(self.ufoPath, "glyphs.layer 1")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
+ self.assertEqual(True, os.path.exists(path))
path = os.path.join(self.ufoPath, "glyphs.layer 2")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
+ self.assertEqual(True, os.path.exists(path))
# layer contents
path = os.path.join(self.ufoPath, "layercontents.plist")
with open(path, "rb") as f:
@@ -4127,7 +4142,7 @@ class UFO3WriteLayersTestCase(unittest.TestCase):
# remove unknown layer
- def testRemoveDefaultLayer(self):
+ def testRemoveDefaultLayer2(self):
self.makeUFO()
writer = UFOWriter(self.ufoPath)
self.assertRaises(UFOLibError, writer.deleteGlyphSet, "does not exist")
@@ -4141,8 +4156,7 @@ class UFO3WriteLayersTestCase(unittest.TestCase):
]
)
writer = UFOWriter(self.ufoPath)
- # if passed bytes string, it'll be decoded to ASCII unicode string
- writer.writeLayerContents(["public.default", "layer 2", b"layer 1"])
+ writer.writeLayerContents(["public.default", "layer 2", "layer 1"])
path = os.path.join(self.ufoPath, "layercontents.plist")
with open(path, "rb") as f:
result = plistlib.load(f)
@@ -4152,8 +4166,8 @@ class UFO3WriteLayersTestCase(unittest.TestCase):
["layer 1", "glyphs.layer 1"],
]
self.assertEqual(expected, result)
- for layerName, directory in result:
- assert isinstance(layerName, unicode)
+ for layerName, _ in result:
+ assert isinstance(layerName, str)
# -----
# /data
@@ -4198,6 +4212,19 @@ class UFO3ReadDataTestCase(unittest.TestCase):
fileObject = reader.getReadFileForPath("data/org.unifiedfontobject.doesNotExist")
self.assertEqual(fileObject, None)
+ def testUFOReaderKernGroupDuplicatesRemoved(self):
+ # Non-kerning group duplicates are kept
+ # Kerning group duplicates are removed
+ expected_groups = {
+ "group1" : ["A"],
+ "group2" : ["B", "C", "B"],
+ "public.kern1.A" : ["A"],
+ "public.kern2.B" : ["B", "A", "C"],
+ }
+ reader = UFOReader(self.getFontPath())
+ groups = reader.readGroups()
+ self.assertEqual(expected_groups, groups)
+
class UFO3WriteDataTestCase(unittest.TestCase):
@@ -4311,7 +4338,7 @@ class UFO3WriteDataTestCase(unittest.TestCase):
# layerinfo.plist
# ---------------
-class TestLayerInfoObject(object):
+class TestLayerInfoObject:
color = guidelines = lib = None
diff --git a/Tests/ufoLib/UFOConversion_test.py b/Tests/ufoLib/UFOConversion_test.py
index 2e288b94..98a08121 100644
--- a/Tests/ufoLib/UFOConversion_test.py
+++ b/Tests/ufoLib/UFOConversion_test.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
import os
import shutil
import unittest
@@ -122,7 +120,7 @@ class ConversionFunctionsTestCase(unittest.TestCase):
# kerning up conversion
# ---------------------
-class TestInfoObject(object): pass
+class TestInfoObject: pass
class KerningUpConversionTestCase(unittest.TestCase):
@@ -139,7 +137,9 @@ class KerningUpConversionTestCase(unittest.TestCase):
("A", "public.kern2.CGroup"): 3,
("A", "public.kern2.DGroup"): 4,
("A", "A"): 1,
- ("A", "B"): 2
+ ("A", "B"): 2,
+ ("X", "A"): 13,
+ ("X", "public.kern2.CGroup"): 14
}
expectedGroups = {
@@ -150,7 +150,8 @@ class KerningUpConversionTestCase(unittest.TestCase):
"public.kern1.CGroup": ["C", "Ccedilla"],
"public.kern2.CGroup": ["C", "Ccedilla"],
"public.kern2.DGroup": ["D"],
- "Not A Kerning Group" : ["A"]
+ "Not A Kerning Group" : ["A"],
+ "X": ["X", "X.sc"]
}
def setUp(self):
@@ -165,6 +166,20 @@ class KerningUpConversionTestCase(unittest.TestCase):
self.clearUFO()
if not os.path.exists(self.ufoPath):
os.mkdir(self.ufoPath)
+
+ # glyphs
+ glyphsPath = os.path.join(self.ufoPath, "glyphs")
+ if not os.path.exists(glyphsPath):
+ os.mkdir(glyphsPath)
+ glyphFile = "X_.glif"
+ glyphsContents = dict(X=glyphFile)
+ path = os.path.join(glyphsPath, "contents.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(glyphsContents, f)
+ path = os.path.join(glyphsPath, glyphFile)
+ with open(path, "w") as f:
+ f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
+
# metainfo.plist
metaInfo = dict(creator="test", formatVersion=formatVersion)
path = os.path.join(self.ufoPath, "metainfo.plist")
@@ -189,6 +204,10 @@ class KerningUpConversionTestCase(unittest.TestCase):
"B" : 10,
"CGroup" : 11,
"DGroup" : 12
+ },
+ "X": {
+ "A" : 13,
+ "CGroup" : 14
}
}
path = os.path.join(self.ufoPath, "kerning.plist")
@@ -199,7 +218,8 @@ class KerningUpConversionTestCase(unittest.TestCase):
"BGroup" : ["B"],
"CGroup" : ["C", "Ccedilla"],
"DGroup" : ["D"],
- "Not A Kerning Group" : ["A"]
+ "Not A Kerning Group" : ["A"],
+ "X" : ["X", "X.sc"] # a group with a name that is also a glyph name
}
path = os.path.join(self.ufoPath, "groups.plist")
with open(path, "wb") as f:
diff --git a/Tests/ufoLib/UFOZ_test.py b/Tests/ufoLib/UFOZ_test.py
index b32bba38..51362106 100644
--- a/Tests/ufoLib/UFOZ_test.py
+++ b/Tests/ufoLib/UFOZ_test.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
from fontTools.misc.py23 import tostr
from fontTools.ufoLib import UFOReader, UFOWriter, UFOFileStructure
from fontTools.ufoLib.errors import UFOLibError, GlifLibError
@@ -39,7 +37,7 @@ def testufoz():
yield tmp.getsyspath(TEST_UFOZ)
-class TestUFOZ(object):
+class TestUFOZ:
def test_read(self, testufoz):
with UFOReader(testufoz) as reader:
@@ -55,7 +53,7 @@ class TestUFOZ(object):
def test_pathlike(testufo):
- class PathLike(object):
+ class PathLike:
def __init__(self, s):
self._path = s
@@ -85,7 +83,7 @@ def memufo():
return m
-class TestMemoryFS(object):
+class TestMemoryFS:
def test_init_reader(self, memufo):
with UFOReader(memufo) as reader:
diff --git a/Tests/ufoLib/filenames_test.py b/Tests/ufoLib/filenames_test.py
index a1d6d92a..a9415a1c 100644
--- a/Tests/ufoLib/filenames_test.py
+++ b/Tests/ufoLib/filenames_test.py
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, unicode_literals
import unittest
from fontTools.ufoLib.filenames import userNameToFileName, handleClash1, handleClash2
diff --git a/Tests/ufoLib/glifLib_test.py b/Tests/ufoLib/glifLib_test.py
index 3bcd07c1..3af0256c 100644
--- a/Tests/ufoLib/glifLib_test.py
+++ b/Tests/ufoLib/glifLib_test.py
@@ -1,4 +1,4 @@
-from __future__ import absolute_import, unicode_literals
+import logging
import os
import tempfile
import shutil
@@ -8,7 +8,10 @@ from .testSupport import getDemoFontGlyphSetPath
from fontTools.ufoLib.glifLib import (
GlyphSet, glyphNameToFileName, readGlyphFromString, writeGlyphToString,
)
+from fontTools.ufoLib.errors import GlifLibError, UnsupportedGLIFFormat, UnsupportedUFOFormat
from fontTools.misc.etree import XML_DECLARATION
+from fontTools.pens.recordingPen import RecordingPointPen
+import pytest
GLYPHSETDIR = getDemoFontGlyphSetPath()
@@ -51,6 +54,16 @@ class GlyphSetTests(unittest.TestCase):
added, removed,
"%s.glif file differs after round tripping" % glyphName)
+ def testContentsExist(self):
+ with self.assertRaises(GlifLibError):
+ GlyphSet(
+ self.dstDir,
+ ufoFormatVersion=2,
+ validateRead=True,
+ validateWrite=True,
+ expectContentsFile=True,
+ )
+
def testRebuildContents(self):
gset = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
contents = gset.contents
@@ -109,43 +122,65 @@ class GlyphSetTests(unittest.TestCase):
self.assertEqual(g.unicodes, unicodes[glyphName])
-class FileNameTests(unittest.TestCase):
-
- def testDefaultFileNameScheme(self):
- self.assertEqual(glyphNameToFileName("a", None), "a.glif")
- self.assertEqual(glyphNameToFileName("A", None), "A_.glif")
- self.assertEqual(glyphNameToFileName("Aring", None), "A_ring.glif")
- self.assertEqual(glyphNameToFileName("F_A_B", None), "F__A__B_.glif")
- self.assertEqual(glyphNameToFileName("A.alt", None), "A_.alt.glif")
- self.assertEqual(glyphNameToFileName("A.Alt", None), "A_.A_lt.glif")
- self.assertEqual(glyphNameToFileName(".notdef", None), "_notdef.glif")
- self.assertEqual(glyphNameToFileName("T_H", None), "T__H_.glif")
- self.assertEqual(glyphNameToFileName("T_h", None), "T__h.glif")
- self.assertEqual(glyphNameToFileName("t_h", None), "t_h.glif")
- self.assertEqual(glyphNameToFileName("F_F_I", None), "F__F__I_.glif")
- self.assertEqual(glyphNameToFileName("f_f_i", None), "f_f_i.glif")
- self.assertEqual(glyphNameToFileName("AE", None), "A_E_.glif")
- self.assertEqual(glyphNameToFileName("Ae", None), "A_e.glif")
- self.assertEqual(glyphNameToFileName("ae", None), "ae.glif")
- self.assertEqual(glyphNameToFileName("aE", None), "aE_.glif")
- self.assertEqual(glyphNameToFileName("a.alt", None), "a.alt.glif")
- self.assertEqual(glyphNameToFileName("A.aLt", None), "A_.aL_t.glif")
- self.assertEqual(glyphNameToFileName("A.alT", None), "A_.alT_.glif")
- self.assertEqual(glyphNameToFileName("Aacute_V.swash", None), "A_acute_V_.swash.glif")
- self.assertEqual(glyphNameToFileName(".notdef", None), "_notdef.glif")
- self.assertEqual(glyphNameToFileName("con", None), "_con.glif")
- self.assertEqual(glyphNameToFileName("CON", None), "C_O_N_.glif")
- self.assertEqual(glyphNameToFileName("con.alt", None), "_con.alt.glif")
- self.assertEqual(glyphNameToFileName("alt.con", None), "alt._con.glif")
-
-
-class _Glyph(object):
+class FileNameTest:
+
+ def test_default_file_name_scheme(self):
+ assert glyphNameToFileName("a", None) == "a.glif"
+ assert glyphNameToFileName("A", None) == "A_.glif"
+ assert glyphNameToFileName("Aring", None) == "A_ring.glif"
+ assert glyphNameToFileName("F_A_B", None) == "F__A__B_.glif"
+ assert glyphNameToFileName("A.alt", None) == "A_.alt.glif"
+ assert glyphNameToFileName("A.Alt", None) == "A_.A_lt.glif"
+ assert glyphNameToFileName(".notdef", None) == "_notdef.glif"
+ assert glyphNameToFileName("T_H", None) =="T__H_.glif"
+ assert glyphNameToFileName("T_h", None) =="T__h.glif"
+ assert glyphNameToFileName("t_h", None) =="t_h.glif"
+ assert glyphNameToFileName("F_F_I", None) == "F__F__I_.glif"
+ assert glyphNameToFileName("f_f_i", None) == "f_f_i.glif"
+ assert glyphNameToFileName("AE", None) == "A_E_.glif"
+ assert glyphNameToFileName("Ae", None) == "A_e.glif"
+ assert glyphNameToFileName("ae", None) == "ae.glif"
+ assert glyphNameToFileName("aE", None) == "aE_.glif"
+ assert glyphNameToFileName("a.alt", None) == "a.alt.glif"
+ assert glyphNameToFileName("A.aLt", None) == "A_.aL_t.glif"
+ assert glyphNameToFileName("A.alT", None) == "A_.alT_.glif"
+ assert glyphNameToFileName("Aacute_V.swash", None) == "A_acute_V_.swash.glif"
+ assert glyphNameToFileName(".notdef", None) == "_notdef.glif"
+ assert glyphNameToFileName("con", None) == "_con.glif"
+ assert glyphNameToFileName("CON", None) == "C_O_N_.glif"
+ assert glyphNameToFileName("con.alt", None) == "_con.alt.glif"
+ assert glyphNameToFileName("alt.con", None) == "alt._con.glif"
+
+ def test_conflicting_case_insensitive_file_names(self, tmp_path):
+ src = GlyphSet(GLYPHSETDIR)
+ dst = GlyphSet(tmp_path)
+ glyph = src["a"]
+
+ dst.writeGlyph("a", glyph)
+ dst.writeGlyph("A", glyph)
+ dst.writeGlyph("a_", glyph)
+ dst.writeGlyph("A_", glyph)
+ dst.writeGlyph("i_j", glyph)
+
+ assert dst.contents == {
+ 'a': 'a.glif',
+ 'A': 'A_.glif',
+ 'a_': 'a_000000000000001.glif',
+ 'A_': 'A__.glif',
+ 'i_j': 'i_j.glif',
+ }
+
+ # make sure filenames are unique even on case-insensitive filesystems
+ assert len({fileName.lower() for fileName in dst.contents.values()}) == 5
+
+
+class _Glyph:
pass
-class ReadWriteFuncTest(unittest.TestCase):
+class ReadWriteFuncTest:
- def testRoundTrip(self):
+ def test_roundtrip(self):
glyph = _Glyph()
glyph.name = "a"
glyph.unicodes = [0x0061]
@@ -154,11 +189,126 @@ class ReadWriteFuncTest(unittest.TestCase):
glyph2 = _Glyph()
readGlyphFromString(s1, glyph2)
- self.assertEqual(glyph.__dict__, glyph2.__dict__)
+ assert glyph.__dict__ == glyph2.__dict__
s2 = writeGlyphToString(glyph2.name, glyph2)
- self.assertEqual(s1, s2)
+ assert s1 == s2
- def testXmlDeclaration(self):
+ def test_xml_declaration(self):
s = writeGlyphToString("a", _Glyph())
- self.assertTrue(s.startswith(XML_DECLARATION % "UTF-8"))
+ assert s.startswith(XML_DECLARATION % "UTF-8")
+
+ def test_parse_xml_remove_comments(self):
+ s = b"""<?xml version='1.0' encoding='UTF-8'?>
+ <!-- a comment -->
+ <glyph name="A" format="2">
+ <advance width="1290"/>
+ <unicode hex="0041"/>
+ <!-- another comment -->
+ </glyph>
+ """
+
+ g = _Glyph()
+ readGlyphFromString(s, g)
+
+ assert g.name == "A"
+ assert g.width == 1290
+ assert g.unicodes == [0x0041]
+
+ def test_read_unsupported_format_version(self, caplog):
+ s = """<?xml version='1.0' encoding='utf-8'?>
+ <glyph name="A" format="0" formatMinor="0">
+ <advance width="500"/>
+ <unicode hex="0041"/>
+ </glyph>
+ """
+
+ with pytest.raises(UnsupportedGLIFFormat):
+ readGlyphFromString(s, _Glyph()) # validate=True by default
+
+ with pytest.raises(UnsupportedGLIFFormat):
+ readGlyphFromString(s, _Glyph(), validate=True)
+
+ caplog.clear()
+ with caplog.at_level(logging.WARNING, logger="fontTools.ufoLib.glifLib"):
+ readGlyphFromString(s, _Glyph(), validate=False)
+
+ assert len(caplog.records) == 1
+ assert "Unsupported GLIF format" in caplog.text
+ assert "Assuming the latest supported version" in caplog.text
+
+ def test_read_allow_format_versions(self):
+ s = """<?xml version='1.0' encoding='utf-8'?>
+ <glyph name="A" format="2">
+ <advance width="500"/>
+ <unicode hex="0041"/>
+ </glyph>
+ """
+
+ # these two calls are are equivalent
+ readGlyphFromString(s, _Glyph(), formatVersions=[1, 2])
+ readGlyphFromString(s, _Glyph(), formatVersions=[(1, 0), (2, 0)])
+
+ # if at least one supported formatVersion, unsupported ones are ignored
+ readGlyphFromString(s, _Glyph(), formatVersions=[(2, 0), (123, 456)])
+
+ with pytest.raises(
+ ValueError,
+ match="None of the requested GLIF formatVersions are supported"
+ ):
+ readGlyphFromString(s, _Glyph(), formatVersions=[0, 2001])
+
+ with pytest.raises(GlifLibError, match="Forbidden GLIF format version"):
+ readGlyphFromString(s, _Glyph(), formatVersions=[1])
+
+ def test_read_ensure_x_y(self):
+ """Ensure that a proper GlifLibError is raised when point coordinates are
+ missing, regardless of validation setting."""
+
+ s = """<?xml version='1.0' encoding='utf-8'?>
+ <glyph name="A" format="2">
+ <outline>
+ <contour>
+ <point x="545" y="0" type="line"/>
+ <point x="638" type="line"/>
+ </contour>
+ </outline>
+ </glyph>
+ """
+ pen = RecordingPointPen()
+
+ with pytest.raises(GlifLibError, match="Required y attribute"):
+ readGlyphFromString(s, _Glyph(), pen)
+
+ with pytest.raises(GlifLibError, match="Required y attribute"):
+ readGlyphFromString(s, _Glyph(), pen, validate=False)
+
+def test_GlyphSet_unsupported_ufoFormatVersion(tmp_path, caplog):
+ with pytest.raises(UnsupportedUFOFormat):
+ GlyphSet(tmp_path, ufoFormatVersion=0)
+ with pytest.raises(UnsupportedUFOFormat):
+ GlyphSet(tmp_path, ufoFormatVersion=(0, 1))
+
+
+def test_GlyphSet_writeGlyph_formatVersion(tmp_path):
+ src = GlyphSet(GLYPHSETDIR)
+ dst = GlyphSet(tmp_path, ufoFormatVersion=(2, 0))
+ glyph = src["A"]
+
+ # no explicit formatVersion passed: use the more recent GLIF formatVersion
+ # that is supported by given ufoFormatVersion (GLIF 1 for UFO 2)
+ dst.writeGlyph("A", glyph)
+ glif = dst.getGLIF("A")
+ assert b'format="1"' in glif
+ assert b'formatMinor' not in glif # omitted when 0
+
+ # explicit, unknown formatVersion
+ with pytest.raises(UnsupportedGLIFFormat):
+ dst.writeGlyph("A", glyph, formatVersion=(0, 0))
+
+ # explicit, known formatVersion but unsupported by given ufoFormatVersion
+ with pytest.raises(
+ UnsupportedGLIFFormat,
+ match="Unsupported GLIF format version .*for UFO format version",
+ ):
+ dst.writeGlyph("A", glyph, formatVersion=(2, 0))
diff --git a/Tests/ufoLib/testSupport.py b/Tests/ufoLib/testSupport.py
index 2982ce84..49f6a539 100755
--- a/Tests/ufoLib/testSupport.py
+++ b/Tests/ufoLib/testSupport.py
@@ -1,13 +1,8 @@
"""Miscellaneous helpers for our test suite."""
-from __future__ import absolute_import, unicode_literals
import os
from fontTools.ufoLib.utils import numberTypes
-try:
- basestring
-except NameError:
- basestring = str
def getDemoFontPath():
"""Return the path to Data/DemoFont.ufo/."""
@@ -22,7 +17,7 @@ def getDemoFontGlyphSetPath():
# GLIF test tools
-class Glyph(object):
+class Glyph:
def __init__(self):
self.name = None
@@ -40,11 +35,11 @@ class Glyph(object):
args = _listToString(args)
kwargs = _dictToString(kwargs)
if args and kwargs:
- return "pointPen.%s(*%s, **%s)" % (command, args, kwargs)
+ return f"pointPen.{command}(*{args}, **{kwargs})"
elif len(args):
- return "pointPen.%s(*%s)" % (command, args)
+ return f"pointPen.{command}(*{args})"
elif len(kwargs):
- return "pointPen.%s(**%s)" % (command, kwargs)
+ return f"pointPen.{command}(**{kwargs})"
else:
return "pointPen.%s()" % command
@@ -103,9 +98,9 @@ def _dictToString(d):
value = _tupleToString(value)
elif isinstance(value, numberTypes):
value = repr(value)
- elif isinstance(value, basestring):
+ elif isinstance(value, str):
value = "\"%s\"" % value
- text.append("%s : %s" % (key, value))
+ text.append(f"{key} : {value}")
if not text:
return ""
return "{%s}" % ", ".join(text)
@@ -121,7 +116,7 @@ def _listToString(l):
value = _tupleToString(value)
elif isinstance(value, numberTypes):
value = repr(value)
- elif isinstance(value, basestring):
+ elif isinstance(value, str):
value = "\"%s\"" % value
text.append(value)
if not text:
@@ -139,7 +134,7 @@ def _tupleToString(t):
value = _tupleToString(value)
elif isinstance(value, numberTypes):
value = repr(value)
- elif isinstance(value, basestring):
+ elif isinstance(value, str):
value = "\"%s\"" % value
text.append(value)
if not text:
diff --git a/Tests/ufoLib/testdata/TestFont1 (UFO3).ufoz b/Tests/ufoLib/testdata/TestFont1 (UFO3).ufoz
new file mode 100644
index 00000000..d78d494f
--- /dev/null
+++ b/Tests/ufoLib/testdata/TestFont1 (UFO3).ufoz
Binary files differ
diff --git a/Tests/ufoLib/testdata/UFO3-Read Data.ufo/groups.plist b/Tests/ufoLib/testdata/UFO3-Read Data.ufo/groups.plist
new file mode 100644
index 00000000..d61c2816
--- /dev/null
+++ b/Tests/ufoLib/testdata/UFO3-Read Data.ufo/groups.plist
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>group1</key>
+ <array>
+ <string>A</string>
+ </array>
+ <key>group2</key>
+ <array>
+ <string>B</string>
+ <string>C</string>
+ <string>B</string>
+ </array>
+ <key>public.kern1.A</key>
+ <array>
+ <string>A</string>
+ </array>
+ <key>public.kern2.B</key>
+ <array>
+ <string>B</string>
+ <string>A</string>
+ <string>B</string>
+ <string>A</string>
+ <string>C</string>
+ </array>
+</dict>
+</plist>
diff --git a/Tests/ufoLib/ufoLib_test.py b/Tests/ufoLib/ufoLib_test.py
new file mode 100644
index 00000000..430e7a7d
--- /dev/null
+++ b/Tests/ufoLib/ufoLib_test.py
@@ -0,0 +1,98 @@
+import logging
+import shutil
+
+from fontTools.misc import plistlib
+from fontTools.ufoLib import UFOReader, UFOWriter, UFOFormatVersion
+from fontTools.ufoLib.errors import UFOLibError, UnsupportedUFOFormat
+import pytest
+
+
+@pytest.fixture
+def ufo_path(tmp_path):
+ ufodir = tmp_path / "TestFont.ufo"
+ ufodir.mkdir()
+ with (ufodir / "metainfo.plist").open("wb") as f:
+ plistlib.dump({"creator": "pytest", "formatVersion": 3}, f)
+ (ufodir / "glyphs").mkdir()
+ with (ufodir / "layercontents.plist").open("wb") as f:
+ plistlib.dump([("public.default", "glyphs")], f)
+ return ufodir
+
+
+def test_formatVersion_deprecated(ufo_path):
+ reader = UFOReader(ufo_path)
+
+ with pytest.warns(DeprecationWarning) as warnings:
+ assert reader.formatVersion == 3
+
+ assert len(warnings) == 1
+ assert "is deprecated; use the 'formatVersionTuple'" in warnings[0].message.args[0]
+
+
+def test_formatVersionTuple(ufo_path):
+ reader = UFOReader(ufo_path)
+
+ assert reader.formatVersionTuple == (3, 0)
+ assert reader.formatVersionTuple.major == 3
+ assert reader.formatVersionTuple.minor == 0
+ assert str(reader.formatVersionTuple) == "3.0"
+
+
+def test_readMetaInfo_errors(ufo_path):
+ (ufo_path / "metainfo.plist").unlink()
+ with pytest.raises(UFOLibError, match="'metainfo.plist' is missing"):
+ UFOReader(ufo_path)
+
+ (ufo_path / "metainfo.plist").write_bytes(plistlib.dumps({}))
+ with pytest.raises(UFOLibError, match="Missing required formatVersion"):
+ UFOReader(ufo_path)
+
+ (ufo_path / "metainfo.plist").write_bytes(plistlib.dumps([]))
+ with pytest.raises(UFOLibError, match="metainfo.plist is not properly formatted"):
+ UFOReader(ufo_path)
+
+
+def test_readMetaInfo_unsupported_format_version(ufo_path, caplog):
+ metainfo = {"formatVersion": 10, "formatVersionMinor": 15}
+ (ufo_path / "metainfo.plist").write_bytes(plistlib.dumps(metainfo))
+
+ with pytest.raises(UnsupportedUFOFormat):
+ UFOReader(ufo_path) # validate=True by default
+
+ with pytest.raises(UnsupportedUFOFormat):
+ UFOReader(ufo_path, validate=True)
+
+ caplog.clear()
+ with caplog.at_level(logging.WARNING, logger="fontTools.ufoLib"):
+ UFOReader(ufo_path, validate=False)
+
+ assert len(caplog.records) == 1
+ assert "Unsupported UFO format" in caplog.text
+ assert "Assuming the latest supported version" in caplog.text
+
+
+def test_UFOWriter_formatVersion(tmp_path):
+ ufo_path = tmp_path / "TestFont.ufo"
+ with UFOWriter(ufo_path, formatVersion=3) as writer:
+ assert writer.formatVersionTuple == (3, 0)
+
+ shutil.rmtree(str(ufo_path))
+ with UFOWriter(ufo_path, formatVersion=(2, 0)) as writer:
+ assert writer.formatVersionTuple == (2, 0)
+
+
+def test_UFOWriter_formatVersion_default_latest(tmp_path):
+ writer = UFOWriter(tmp_path / "TestFont.ufo")
+ assert writer.formatVersionTuple == UFOFormatVersion.default()
+
+
+def test_UFOWriter_unsupported_format_version(tmp_path):
+ with pytest.raises(UnsupportedUFOFormat):
+ UFOWriter(tmp_path, formatVersion=(123, 456))
+
+
+def test_UFOWriter_previous_higher_format_version(ufo_path):
+ with pytest.raises(
+ UnsupportedUFOFormat, match="UFO located at this path is a higher version"
+ ):
+ UFOWriter(ufo_path, formatVersion=(2, 0))
diff --git a/Tests/unicodedata_test.py b/Tests/unicodedata_test.py
index fde3338f..05f7de68 100644
--- a/Tests/unicodedata_test.py
+++ b/Tests/unicodedata_test.py
@@ -1,7 +1,3 @@
-from __future__ import (
- print_function, division, absolute_import, unicode_literals)
-from fontTools.misc.py23 import *
-
from fontTools import unicodedata
import pytest
@@ -9,161 +5,161 @@ import pytest
def test_script():
assert unicodedata.script("a") == "Latn"
- assert unicodedata.script(unichr(0)) == "Zyyy"
- assert unicodedata.script(unichr(0x0378)) == "Zzzz"
- assert unicodedata.script(unichr(0x10FFFF)) == "Zzzz"
+ assert unicodedata.script(chr(0)) == "Zyyy"
+ assert unicodedata.script(chr(0x0378)) == "Zzzz"
+ assert unicodedata.script(chr(0x10FFFF)) == "Zzzz"
# these were randomly sampled, one character per script
- assert unicodedata.script(unichr(0x1E918)) == 'Adlm'
- assert unicodedata.script(unichr(0x1170D)) == 'Ahom'
- assert unicodedata.script(unichr(0x145A0)) == 'Hluw'
- assert unicodedata.script(unichr(0x0607)) == 'Arab'
- assert unicodedata.script(unichr(0x056C)) == 'Armn'
- assert unicodedata.script(unichr(0x10B27)) == 'Avst'
- assert unicodedata.script(unichr(0x1B41)) == 'Bali'
- assert unicodedata.script(unichr(0x168AD)) == 'Bamu'
- assert unicodedata.script(unichr(0x16ADD)) == 'Bass'
- assert unicodedata.script(unichr(0x1BE5)) == 'Batk'
- assert unicodedata.script(unichr(0x09F3)) == 'Beng'
- assert unicodedata.script(unichr(0x11C5B)) == 'Bhks'
- assert unicodedata.script(unichr(0x3126)) == 'Bopo'
- assert unicodedata.script(unichr(0x1103B)) == 'Brah'
- assert unicodedata.script(unichr(0x2849)) == 'Brai'
- assert unicodedata.script(unichr(0x1A0A)) == 'Bugi'
- assert unicodedata.script(unichr(0x174E)) == 'Buhd'
- assert unicodedata.script(unichr(0x18EE)) == 'Cans'
- assert unicodedata.script(unichr(0x102B7)) == 'Cari'
- assert unicodedata.script(unichr(0x1053D)) == 'Aghb'
- assert unicodedata.script(unichr(0x11123)) == 'Cakm'
- assert unicodedata.script(unichr(0xAA1F)) == 'Cham'
- assert unicodedata.script(unichr(0xAB95)) == 'Cher'
- assert unicodedata.script(unichr(0x1F0C7)) == 'Zyyy'
- assert unicodedata.script(unichr(0x2C85)) == 'Copt'
- assert unicodedata.script(unichr(0x12014)) == 'Xsux'
- assert unicodedata.script(unichr(0x1082E)) == 'Cprt'
- assert unicodedata.script(unichr(0xA686)) == 'Cyrl'
- assert unicodedata.script(unichr(0x10417)) == 'Dsrt'
- assert unicodedata.script(unichr(0x093E)) == 'Deva'
- assert unicodedata.script(unichr(0x1BC4B)) == 'Dupl'
- assert unicodedata.script(unichr(0x1310C)) == 'Egyp'
- assert unicodedata.script(unichr(0x1051C)) == 'Elba'
- assert unicodedata.script(unichr(0x2DA6)) == 'Ethi'
- assert unicodedata.script(unichr(0x10AD)) == 'Geor'
- assert unicodedata.script(unichr(0x2C52)) == 'Glag'
- assert unicodedata.script(unichr(0x10343)) == 'Goth'
- assert unicodedata.script(unichr(0x11371)) == 'Gran'
- assert unicodedata.script(unichr(0x03D0)) == 'Grek'
- assert unicodedata.script(unichr(0x0AAA)) == 'Gujr'
- assert unicodedata.script(unichr(0x0A4C)) == 'Guru'
- assert unicodedata.script(unichr(0x23C9F)) == 'Hani'
- assert unicodedata.script(unichr(0xC259)) == 'Hang'
- assert unicodedata.script(unichr(0x1722)) == 'Hano'
- assert unicodedata.script(unichr(0x108F5)) == 'Hatr'
- assert unicodedata.script(unichr(0x05C2)) == 'Hebr'
- assert unicodedata.script(unichr(0x1B072)) == 'Hira'
- assert unicodedata.script(unichr(0x10847)) == 'Armi'
- assert unicodedata.script(unichr(0x033A)) == 'Zinh'
- assert unicodedata.script(unichr(0x10B66)) == 'Phli'
- assert unicodedata.script(unichr(0x10B4B)) == 'Prti'
- assert unicodedata.script(unichr(0xA98A)) == 'Java'
- assert unicodedata.script(unichr(0x110B2)) == 'Kthi'
- assert unicodedata.script(unichr(0x0CC6)) == 'Knda'
- assert unicodedata.script(unichr(0x3337)) == 'Kana'
- assert unicodedata.script(unichr(0xA915)) == 'Kali'
- assert unicodedata.script(unichr(0x10A2E)) == 'Khar'
- assert unicodedata.script(unichr(0x17AA)) == 'Khmr'
- assert unicodedata.script(unichr(0x11225)) == 'Khoj'
- assert unicodedata.script(unichr(0x112B6)) == 'Sind'
- assert unicodedata.script(unichr(0x0ED7)) == 'Laoo'
- assert unicodedata.script(unichr(0xAB3C)) == 'Latn'
- assert unicodedata.script(unichr(0x1C48)) == 'Lepc'
- assert unicodedata.script(unichr(0x1923)) == 'Limb'
- assert unicodedata.script(unichr(0x1071D)) == 'Lina'
- assert unicodedata.script(unichr(0x100EC)) == 'Linb'
- assert unicodedata.script(unichr(0xA4E9)) == 'Lisu'
- assert unicodedata.script(unichr(0x10284)) == 'Lyci'
- assert unicodedata.script(unichr(0x10926)) == 'Lydi'
- assert unicodedata.script(unichr(0x11161)) == 'Mahj'
- assert unicodedata.script(unichr(0x0D56)) == 'Mlym'
- assert unicodedata.script(unichr(0x0856)) == 'Mand'
- assert unicodedata.script(unichr(0x10AF0)) == 'Mani'
- assert unicodedata.script(unichr(0x11CB0)) == 'Marc'
- assert unicodedata.script(unichr(0x11D28)) == 'Gonm'
- assert unicodedata.script(unichr(0xABDD)) == 'Mtei'
- assert unicodedata.script(unichr(0x1E897)) == 'Mend'
- assert unicodedata.script(unichr(0x109B0)) == 'Merc'
- assert unicodedata.script(unichr(0x10993)) == 'Mero'
- assert unicodedata.script(unichr(0x16F5D)) == 'Plrd'
- assert unicodedata.script(unichr(0x1160B)) == 'Modi'
- assert unicodedata.script(unichr(0x18A8)) == 'Mong'
- assert unicodedata.script(unichr(0x16A48)) == 'Mroo'
- assert unicodedata.script(unichr(0x1128C)) == 'Mult'
- assert unicodedata.script(unichr(0x105B)) == 'Mymr'
- assert unicodedata.script(unichr(0x108AF)) == 'Nbat'
- assert unicodedata.script(unichr(0x19B3)) == 'Talu'
- assert unicodedata.script(unichr(0x1143D)) == 'Newa'
- assert unicodedata.script(unichr(0x07F4)) == 'Nkoo'
- assert unicodedata.script(unichr(0x1B192)) == 'Nshu'
- assert unicodedata.script(unichr(0x169C)) == 'Ogam'
- assert unicodedata.script(unichr(0x1C56)) == 'Olck'
- assert unicodedata.script(unichr(0x10CE9)) == 'Hung'
- assert unicodedata.script(unichr(0x10316)) == 'Ital'
- assert unicodedata.script(unichr(0x10A93)) == 'Narb'
- assert unicodedata.script(unichr(0x1035A)) == 'Perm'
- assert unicodedata.script(unichr(0x103D5)) == 'Xpeo'
- assert unicodedata.script(unichr(0x10A65)) == 'Sarb'
- assert unicodedata.script(unichr(0x10C09)) == 'Orkh'
- assert unicodedata.script(unichr(0x0B60)) == 'Orya'
- assert unicodedata.script(unichr(0x104CF)) == 'Osge'
- assert unicodedata.script(unichr(0x104A8)) == 'Osma'
- assert unicodedata.script(unichr(0x16B12)) == 'Hmng'
- assert unicodedata.script(unichr(0x10879)) == 'Palm'
- assert unicodedata.script(unichr(0x11AF1)) == 'Pauc'
- assert unicodedata.script(unichr(0xA869)) == 'Phag'
- assert unicodedata.script(unichr(0x10909)) == 'Phnx'
- assert unicodedata.script(unichr(0x10B81)) == 'Phlp'
- assert unicodedata.script(unichr(0xA941)) == 'Rjng'
- assert unicodedata.script(unichr(0x16C3)) == 'Runr'
- assert unicodedata.script(unichr(0x0814)) == 'Samr'
- assert unicodedata.script(unichr(0xA88C)) == 'Saur'
- assert unicodedata.script(unichr(0x111C8)) == 'Shrd'
- assert unicodedata.script(unichr(0x1045F)) == 'Shaw'
- assert unicodedata.script(unichr(0x115AD)) == 'Sidd'
- assert unicodedata.script(unichr(0x1D8C0)) == 'Sgnw'
- assert unicodedata.script(unichr(0x0DB9)) == 'Sinh'
- assert unicodedata.script(unichr(0x110F9)) == 'Sora'
- assert unicodedata.script(unichr(0x11A60)) == 'Soyo'
- assert unicodedata.script(unichr(0x1B94)) == 'Sund'
- assert unicodedata.script(unichr(0xA81F)) == 'Sylo'
- assert unicodedata.script(unichr(0x0740)) == 'Syrc'
- assert unicodedata.script(unichr(0x1714)) == 'Tglg'
- assert unicodedata.script(unichr(0x1761)) == 'Tagb'
- assert unicodedata.script(unichr(0x1965)) == 'Tale'
- assert unicodedata.script(unichr(0x1A32)) == 'Lana'
- assert unicodedata.script(unichr(0xAA86)) == 'Tavt'
- assert unicodedata.script(unichr(0x116A5)) == 'Takr'
- assert unicodedata.script(unichr(0x0B8E)) == 'Taml'
- assert unicodedata.script(unichr(0x1754D)) == 'Tang'
- assert unicodedata.script(unichr(0x0C40)) == 'Telu'
- assert unicodedata.script(unichr(0x07A4)) == 'Thaa'
- assert unicodedata.script(unichr(0x0E42)) == 'Thai'
- assert unicodedata.script(unichr(0x0F09)) == 'Tibt'
- assert unicodedata.script(unichr(0x2D3A)) == 'Tfng'
- assert unicodedata.script(unichr(0x114B0)) == 'Tirh'
- assert unicodedata.script(unichr(0x1038B)) == 'Ugar'
- assert unicodedata.script(unichr(0xA585)) == 'Vaii'
- assert unicodedata.script(unichr(0x118CF)) == 'Wara'
- assert unicodedata.script(unichr(0xA066)) == 'Yiii'
- assert unicodedata.script(unichr(0x11A31)) == 'Zanb'
+ assert unicodedata.script(chr(0x1E918)) == 'Adlm'
+ assert unicodedata.script(chr(0x1170D)) == 'Ahom'
+ assert unicodedata.script(chr(0x145A0)) == 'Hluw'
+ assert unicodedata.script(chr(0x0607)) == 'Arab'
+ assert unicodedata.script(chr(0x056C)) == 'Armn'
+ assert unicodedata.script(chr(0x10B27)) == 'Avst'
+ assert unicodedata.script(chr(0x1B41)) == 'Bali'
+ assert unicodedata.script(chr(0x168AD)) == 'Bamu'
+ assert unicodedata.script(chr(0x16ADD)) == 'Bass'
+ assert unicodedata.script(chr(0x1BE5)) == 'Batk'
+ assert unicodedata.script(chr(0x09F3)) == 'Beng'
+ assert unicodedata.script(chr(0x11C5B)) == 'Bhks'
+ assert unicodedata.script(chr(0x3126)) == 'Bopo'
+ assert unicodedata.script(chr(0x1103B)) == 'Brah'
+ assert unicodedata.script(chr(0x2849)) == 'Brai'
+ assert unicodedata.script(chr(0x1A0A)) == 'Bugi'
+ assert unicodedata.script(chr(0x174E)) == 'Buhd'
+ assert unicodedata.script(chr(0x18EE)) == 'Cans'
+ assert unicodedata.script(chr(0x102B7)) == 'Cari'
+ assert unicodedata.script(chr(0x1053D)) == 'Aghb'
+ assert unicodedata.script(chr(0x11123)) == 'Cakm'
+ assert unicodedata.script(chr(0xAA1F)) == 'Cham'
+ assert unicodedata.script(chr(0xAB95)) == 'Cher'
+ assert unicodedata.script(chr(0x1F0C7)) == 'Zyyy'
+ assert unicodedata.script(chr(0x2C85)) == 'Copt'
+ assert unicodedata.script(chr(0x12014)) == 'Xsux'
+ assert unicodedata.script(chr(0x1082E)) == 'Cprt'
+ assert unicodedata.script(chr(0xA686)) == 'Cyrl'
+ assert unicodedata.script(chr(0x10417)) == 'Dsrt'
+ assert unicodedata.script(chr(0x093E)) == 'Deva'
+ assert unicodedata.script(chr(0x1BC4B)) == 'Dupl'
+ assert unicodedata.script(chr(0x1310C)) == 'Egyp'
+ assert unicodedata.script(chr(0x1051C)) == 'Elba'
+ assert unicodedata.script(chr(0x2DA6)) == 'Ethi'
+ assert unicodedata.script(chr(0x10AD)) == 'Geor'
+ assert unicodedata.script(chr(0x2C52)) == 'Glag'
+ assert unicodedata.script(chr(0x10343)) == 'Goth'
+ assert unicodedata.script(chr(0x11371)) == 'Gran'
+ assert unicodedata.script(chr(0x03D0)) == 'Grek'
+ assert unicodedata.script(chr(0x0AAA)) == 'Gujr'
+ assert unicodedata.script(chr(0x0A4C)) == 'Guru'
+ assert unicodedata.script(chr(0x23C9F)) == 'Hani'
+ assert unicodedata.script(chr(0xC259)) == 'Hang'
+ assert unicodedata.script(chr(0x1722)) == 'Hano'
+ assert unicodedata.script(chr(0x108F5)) == 'Hatr'
+ assert unicodedata.script(chr(0x05C2)) == 'Hebr'
+ assert unicodedata.script(chr(0x1B072)) == 'Hira'
+ assert unicodedata.script(chr(0x10847)) == 'Armi'
+ assert unicodedata.script(chr(0x033A)) == 'Zinh'
+ assert unicodedata.script(chr(0x10B66)) == 'Phli'
+ assert unicodedata.script(chr(0x10B4B)) == 'Prti'
+ assert unicodedata.script(chr(0xA98A)) == 'Java'
+ assert unicodedata.script(chr(0x110B2)) == 'Kthi'
+ assert unicodedata.script(chr(0x0CC6)) == 'Knda'
+ assert unicodedata.script(chr(0x3337)) == 'Kana'
+ assert unicodedata.script(chr(0xA915)) == 'Kali'
+ assert unicodedata.script(chr(0x10A2E)) == 'Khar'
+ assert unicodedata.script(chr(0x17AA)) == 'Khmr'
+ assert unicodedata.script(chr(0x11225)) == 'Khoj'
+ assert unicodedata.script(chr(0x112B6)) == 'Sind'
+ assert unicodedata.script(chr(0x0ED7)) == 'Laoo'
+ assert unicodedata.script(chr(0xAB3C)) == 'Latn'
+ assert unicodedata.script(chr(0x1C48)) == 'Lepc'
+ assert unicodedata.script(chr(0x1923)) == 'Limb'
+ assert unicodedata.script(chr(0x1071D)) == 'Lina'
+ assert unicodedata.script(chr(0x100EC)) == 'Linb'
+ assert unicodedata.script(chr(0xA4E9)) == 'Lisu'
+ assert unicodedata.script(chr(0x10284)) == 'Lyci'
+ assert unicodedata.script(chr(0x10926)) == 'Lydi'
+ assert unicodedata.script(chr(0x11161)) == 'Mahj'
+ assert unicodedata.script(chr(0x0D56)) == 'Mlym'
+ assert unicodedata.script(chr(0x0856)) == 'Mand'
+ assert unicodedata.script(chr(0x10AF0)) == 'Mani'
+ assert unicodedata.script(chr(0x11CB0)) == 'Marc'
+ assert unicodedata.script(chr(0x11D28)) == 'Gonm'
+ assert unicodedata.script(chr(0xABDD)) == 'Mtei'
+ assert unicodedata.script(chr(0x1E897)) == 'Mend'
+ assert unicodedata.script(chr(0x109B0)) == 'Merc'
+ assert unicodedata.script(chr(0x10993)) == 'Mero'
+ assert unicodedata.script(chr(0x16F5D)) == 'Plrd'
+ assert unicodedata.script(chr(0x1160B)) == 'Modi'
+ assert unicodedata.script(chr(0x18A8)) == 'Mong'
+ assert unicodedata.script(chr(0x16A48)) == 'Mroo'
+ assert unicodedata.script(chr(0x1128C)) == 'Mult'
+ assert unicodedata.script(chr(0x105B)) == 'Mymr'
+ assert unicodedata.script(chr(0x108AF)) == 'Nbat'
+ assert unicodedata.script(chr(0x19B3)) == 'Talu'
+ assert unicodedata.script(chr(0x1143D)) == 'Newa'
+ assert unicodedata.script(chr(0x07F4)) == 'Nkoo'
+ assert unicodedata.script(chr(0x1B192)) == 'Nshu'
+ assert unicodedata.script(chr(0x169C)) == 'Ogam'
+ assert unicodedata.script(chr(0x1C56)) == 'Olck'
+ assert unicodedata.script(chr(0x10CE9)) == 'Hung'
+ assert unicodedata.script(chr(0x10316)) == 'Ital'
+ assert unicodedata.script(chr(0x10A93)) == 'Narb'
+ assert unicodedata.script(chr(0x1035A)) == 'Perm'
+ assert unicodedata.script(chr(0x103D5)) == 'Xpeo'
+ assert unicodedata.script(chr(0x10A65)) == 'Sarb'
+ assert unicodedata.script(chr(0x10C09)) == 'Orkh'
+ assert unicodedata.script(chr(0x0B60)) == 'Orya'
+ assert unicodedata.script(chr(0x104CF)) == 'Osge'
+ assert unicodedata.script(chr(0x104A8)) == 'Osma'
+ assert unicodedata.script(chr(0x16B12)) == 'Hmng'
+ assert unicodedata.script(chr(0x10879)) == 'Palm'
+ assert unicodedata.script(chr(0x11AF1)) == 'Pauc'
+ assert unicodedata.script(chr(0xA869)) == 'Phag'
+ assert unicodedata.script(chr(0x10909)) == 'Phnx'
+ assert unicodedata.script(chr(0x10B81)) == 'Phlp'
+ assert unicodedata.script(chr(0xA941)) == 'Rjng'
+ assert unicodedata.script(chr(0x16C3)) == 'Runr'
+ assert unicodedata.script(chr(0x0814)) == 'Samr'
+ assert unicodedata.script(chr(0xA88C)) == 'Saur'
+ assert unicodedata.script(chr(0x111C8)) == 'Shrd'
+ assert unicodedata.script(chr(0x1045F)) == 'Shaw'
+ assert unicodedata.script(chr(0x115AD)) == 'Sidd'
+ assert unicodedata.script(chr(0x1D8C0)) == 'Sgnw'
+ assert unicodedata.script(chr(0x0DB9)) == 'Sinh'
+ assert unicodedata.script(chr(0x110F9)) == 'Sora'
+ assert unicodedata.script(chr(0x11A60)) == 'Soyo'
+ assert unicodedata.script(chr(0x1B94)) == 'Sund'
+ assert unicodedata.script(chr(0xA81F)) == 'Sylo'
+ assert unicodedata.script(chr(0x0740)) == 'Syrc'
+ assert unicodedata.script(chr(0x1714)) == 'Tglg'
+ assert unicodedata.script(chr(0x1761)) == 'Tagb'
+ assert unicodedata.script(chr(0x1965)) == 'Tale'
+ assert unicodedata.script(chr(0x1A32)) == 'Lana'
+ assert unicodedata.script(chr(0xAA86)) == 'Tavt'
+ assert unicodedata.script(chr(0x116A5)) == 'Takr'
+ assert unicodedata.script(chr(0x0B8E)) == 'Taml'
+ assert unicodedata.script(chr(0x1754D)) == 'Tang'
+ assert unicodedata.script(chr(0x0C40)) == 'Telu'
+ assert unicodedata.script(chr(0x07A4)) == 'Thaa'
+ assert unicodedata.script(chr(0x0E42)) == 'Thai'
+ assert unicodedata.script(chr(0x0F09)) == 'Tibt'
+ assert unicodedata.script(chr(0x2D3A)) == 'Tfng'
+ assert unicodedata.script(chr(0x114B0)) == 'Tirh'
+ assert unicodedata.script(chr(0x1038B)) == 'Ugar'
+ assert unicodedata.script(chr(0xA585)) == 'Vaii'
+ assert unicodedata.script(chr(0x118CF)) == 'Wara'
+ assert unicodedata.script(chr(0xA066)) == 'Yiii'
+ assert unicodedata.script(chr(0x11A31)) == 'Zanb'
def test_script_extension():
assert unicodedata.script_extension("a") == {"Latn"}
- assert unicodedata.script_extension(unichr(0)) == {"Zyyy"}
- assert unicodedata.script_extension(unichr(0x0378)) == {"Zzzz"}
- assert unicodedata.script_extension(unichr(0x10FFFF)) == {"Zzzz"}
+ assert unicodedata.script_extension(chr(0)) == {"Zyyy"}
+ assert unicodedata.script_extension(chr(0x0378)) == {"Zzzz"}
+ assert unicodedata.script_extension(chr(0x10FFFF)) == {"Zzzz"}
- assert unicodedata.script_extension("\u0660") == {'Arab', 'Thaa'}
+ assert unicodedata.script_extension("\u0660") == {'Arab', 'Thaa', 'Yezi'}
assert unicodedata.script_extension("\u0964") == {
'Beng', 'Deva', 'Dogr', 'Gong', 'Gonm', 'Gran', 'Gujr', 'Guru', 'Knda',
'Mahj', 'Mlym', 'Nand', 'Orya', 'Sind', 'Sinh', 'Sylo', 'Takr', 'Taml',
diff --git a/Tests/varLib/builder_test.py b/Tests/varLib/builder_test.py
index 09fd2903..80607eaa 100644
--- a/Tests/varLib/builder_test.py
+++ b/Tests/varLib/builder_test.py
@@ -1,4 +1,3 @@
-from __future__ import print_function, division, absolute_import
from fontTools.varLib.builder import buildVarData
import pytest
diff --git a/Tests/varLib/data/FeatureVars.designspace b/Tests/varLib/data/FeatureVars.designspace
index 9c958a5a..902fade1 100644
--- a/Tests/varLib/data/FeatureVars.designspace
+++ b/Tests/varLib/data/FeatureVars.designspace
@@ -6,7 +6,7 @@
<labelname xml:lang="en">Contrast</labelname>
</axis>
</axes>
- <rules>
+ <rules processing="last">
<rule name="dollar-stroke">
<conditionset>
<condition name="weight" minimum="500" /> <!-- intentionally omitted maximum -->
diff --git a/Tests/varLib/data/FeatureVarsCustomTag.designspace b/Tests/varLib/data/FeatureVarsCustomTag.designspace
new file mode 100644
index 00000000..45b06f30
--- /dev/null
+++ b/Tests/varLib/data/FeatureVarsCustomTag.designspace
@@ -0,0 +1,77 @@
+<?xml version='1.0' encoding='utf-8'?>
+<designspace format="3">
+ <axes>
+ <axis default="368.0" maximum="1000.0" minimum="0.0" name="weight" tag="wght" />
+ <axis default="0.0" maximum="100.0" minimum="0.0" name="contrast" tag="cntr">
+ <labelname xml:lang="en">Contrast</labelname>
+ </axis>
+ </axes>
+ <rules processing="last">
+ <rule name="dollar-stroke">
+ <conditionset>
+ <condition name="weight" minimum="500" /> <!-- intentionally omitted maximum -->
+ </conditionset>
+ <sub name="uni0024" with="uni0024.nostroke" />
+ </rule>
+ <rule name="to-lowercase">
+ <conditionset>
+ <condition name="contrast" minimum="75" maximum="100" />
+ </conditionset>
+ <sub name="uni0041" with="uni0061" />
+ </rule>
+ <rule name="to-uppercase">
+ <conditionset>
+ <condition name="weight" minimum="0" maximum="200" />
+ <condition name="contrast" minimum="0" maximum="25" />
+ </conditionset>
+ <sub name="uni0061" with="uni0041" />
+ </rule>
+ </rules>
+ <sources>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master0.ufo" name="master_0" stylename="Master0">
+ <location>
+ <dimension name="weight" xvalue="0" />
+ <dimension name="contrast" xvalue="0" />
+ </location>
+ </source>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master1.ufo" name="master_1" stylename="Master1">
+ <lib copy="1" />
+ <groups copy="1" />
+ <info copy="1" />
+ <location>
+ <dimension name="weight" xvalue="368" />
+ <dimension name="contrast" xvalue="0" />
+ </location>
+ </source>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master2.ufo" name="master_2" stylename="Master2">
+ <location>
+ <dimension name="weight" xvalue="1000" />
+ <dimension name="contrast" xvalue="0" />
+ </location>
+ </source>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master3.ufo" name="master_3" stylename="Master3">
+ <location>
+ <dimension name="weight" xvalue="1000" />
+ <dimension name="contrast" xvalue="100" />
+ </location>
+ </source>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master0.ufo" name="master_0" stylename="Master0">
+ <location>
+ <dimension name="weight" xvalue="0" />
+ <dimension name="contrast" xvalue="100" />
+ </location>
+ </source>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master4.ufo" name="master_4" stylename="Master4">
+ <location>
+ <dimension name="weight" xvalue="368" />
+ <dimension name="contrast" xvalue="100" />
+ </location>
+ </source>
+ </sources>
+ <lib>
+ <dict>
+ <key>com.github.fonttools.varLib.featureVarsFeatureTag</key>
+ <string>calt</string>
+ </dict>
+ </lib>
+</designspace>
diff --git a/Tests/varLib/data/FeatureVarsWholeRange.designspace b/Tests/varLib/data/FeatureVarsWholeRange.designspace
new file mode 100644
index 00000000..2d8802cf
--- /dev/null
+++ b/Tests/varLib/data/FeatureVarsWholeRange.designspace
@@ -0,0 +1,34 @@
+<?xml version='1.0' encoding='utf-8'?>
+<designspace format="3">
+ <axes>
+ <axis default="368.0" maximum="1000.0" minimum="0.0" name="weight" tag="wght" />
+ </axes>
+ <rules processing="last">
+ <rule name="always">
+ <conditionset>
+ <condition name="weight" minimum="0" maximum="1000" />
+ </conditionset>
+ <sub name="uni0024" with="uni0024.nostroke" />
+ </rule>
+ </rules>
+ <sources>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master0.ufo" name="master_0" stylename="Master0">
+ <location>
+ <dimension name="weight" xvalue="0" />
+ </location>
+ </source>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master1.ufo" name="master_1" stylename="Master1">
+ <lib copy="1" />
+ <groups copy="1" />
+ <info copy="1" />
+ <location>
+ <dimension name="weight" xvalue="368" />
+ </location>
+ </source>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master3.ufo" name="master_3" stylename="Master3">
+ <location>
+ <dimension name="weight" xvalue="1000" />
+ </location>
+ </source>
+ </sources>
+</designspace>
diff --git a/Tests/varLib/data/FeatureVarsWholeRangeEmpty.designspace b/Tests/varLib/data/FeatureVarsWholeRangeEmpty.designspace
new file mode 100644
index 00000000..a692daa6
--- /dev/null
+++ b/Tests/varLib/data/FeatureVarsWholeRangeEmpty.designspace
@@ -0,0 +1,33 @@
+<?xml version='1.0' encoding='utf-8'?>
+<designspace format="3">
+ <axes>
+ <axis default="368.0" maximum="1000.0" minimum="0.0" name="weight" tag="wght" />
+ </axes>
+ <rules processing="last">
+ <rule name="always">
+ <conditionset>
+ </conditionset>
+ <sub name="uni0024" with="uni0024.nostroke" />
+ </rule>
+ </rules>
+ <sources>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master0.ufo" name="master_0" stylename="Master0">
+ <location>
+ <dimension name="weight" xvalue="0" />
+ </location>
+ </source>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master1.ufo" name="master_1" stylename="Master1">
+ <lib copy="1" />
+ <groups copy="1" />
+ <info copy="1" />
+ <location>
+ <dimension name="weight" xvalue="368" />
+ </location>
+ </source>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master3.ufo" name="master_3" stylename="Master3">
+ <location>
+ <dimension name="weight" xvalue="1000" />
+ </location>
+ </source>
+ </sources>
+</designspace>
diff --git a/Tests/varLib/data/IncompatibleArrays.designspace b/Tests/varLib/data/IncompatibleArrays.designspace
new file mode 100644
index 00000000..399810ea
--- /dev/null
+++ b/Tests/varLib/data/IncompatibleArrays.designspace
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.1">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="200" maximum="700" default="200"/>
+ </axes>
+ <sources>
+ <source filename="master_incompatible_arrays/IncompatibleArrays-Regular.ttx" name="Simple Two Axis Regular" familyname="Simple Two Axis" stylename="Regular">
+ <lib copy="1"/>
+ <groups copy="1"/>
+ <features copy="1"/>
+ <info copy="1"/>
+ <location>
+ <dimension name="Weight" xvalue="200"/>
+ </location>
+ </source>
+ <source filename="master_incompatible_arrays/IncompatibleArrays-Bold.ttx" name="Simple Two Axis Bold" familyname="Simple Two Axis" stylename="Bold">
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ </location>
+ </source>
+ </sources>
+</designspace>
diff --git a/Tests/varLib/data/IncompatibleFeatures.designspace b/Tests/varLib/data/IncompatibleFeatures.designspace
new file mode 100644
index 00000000..ab275164
--- /dev/null
+++ b/Tests/varLib/data/IncompatibleFeatures.designspace
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.1">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="200" maximum="700" default="200"/>
+ </axes>
+ <sources>
+ <source filename="master_incompatible_features/IncompatibleFeatures-Regular.ttx" name="Simple Two Axis Regular" familyname="Simple Two Axis" stylename="Regular">
+ <lib copy="1"/>
+ <groups copy="1"/>
+ <features copy="1"/>
+ <info copy="1"/>
+ <location>
+ <dimension name="Weight" xvalue="200"/>
+ </location>
+ </source>
+ <source filename="master_incompatible_features/IncompatibleFeatures-Bold.ttx" name="Simple Two Axis Bold" familyname="Simple Two Axis" stylename="Bold">
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ </location>
+ </source>
+ </sources>
+</designspace>
diff --git a/Tests/varLib/data/IncompatibleLookupTypes.designspace b/Tests/varLib/data/IncompatibleLookupTypes.designspace
new file mode 100644
index 00000000..c7d35754
--- /dev/null
+++ b/Tests/varLib/data/IncompatibleLookupTypes.designspace
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.1">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="200" maximum="700" default="200"/>
+ </axes>
+ <sources>
+ <source filename="master_incompatible_lookup_types/IncompatibleLookupTypes-Regular.ttx" name="Simple Two Axis Regular" familyname="Simple Two Axis" stylename="Regular">
+ <lib copy="1"/>
+ <groups copy="1"/>
+ <features copy="1"/>
+ <info copy="1"/>
+ <location>
+ <dimension name="Weight" xvalue="200"/>
+ </location>
+ </source>
+ <source filename="master_incompatible_lookup_types/IncompatibleLookupTypes-Bold.ttx" name="Simple Two Axis Bold" familyname="Simple Two Axis" stylename="Bold">
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ </location>
+ </source>
+ </sources>
+</designspace>
diff --git a/Tests/varLib/data/SingleMaster.designspace b/Tests/varLib/data/SingleMaster.designspace
new file mode 100644
index 00000000..53bdeabf
--- /dev/null
+++ b/Tests/varLib/data/SingleMaster.designspace
@@ -0,0 +1,13 @@
+<?xml version='1.0' encoding='utf-8'?>
+<designspace format="3">
+ <axes>
+ <axis default="400" maximum="400" minimum="400" name="weight" tag="wght" />
+ </axes>
+ <sources>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master0.ufo" name="master_0">
+ <location>
+ <dimension name="weight" xvalue="400" />
+ </location>
+ </source>
+ </sources>
+</designspace>
diff --git a/Tests/varLib/data/TestBASE.designspace b/Tests/varLib/data/TestBASE.designspace
new file mode 100644
index 00000000..6a584a5b
--- /dev/null
+++ b/Tests/varLib/data/TestBASE.designspace
@@ -0,0 +1,36 @@
+<?xml version='1.0' encoding='utf-8'?>
+<designspace format="3">
+ <axes>
+ <axis default="0" maximum="900" minimum="0" name="weight" tag="wght" />
+ </axes>
+ <sources>
+ <source filename="master_base_test/TestBASE.0.ufo" stylename="w0.00">
+ <info copy="1" />
+ <location>
+ <dimension name="weight" xvalue="0.00" />
+ </location>
+ </source>
+ <source filename="master_base_test/TestBASE.900.ufo" stylename="w900.00">
+
+ <location>
+ <dimension name="weight" xvalue="900.00" />
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance familyname="TestBASE" filename="instances/TestBASE-Thin.otf" postscriptfontname="TestBASE-Thin" stylename="ExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="TestBASE" filename="instances/TestBase-Black.otf" postscriptfontname="TestBASE-Black" stylename="Heavy">
+ <location>
+ <dimension name="weight" xvalue="900" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/varLib/data/TestCFF2Input.designspace b/Tests/varLib/data/TestCFF2Input.designspace
new file mode 100644
index 00000000..ebbf14aa
--- /dev/null
+++ b/Tests/varLib/data/TestCFF2Input.designspace
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<designspace format="3">
+ <axes>
+ <axis default="400.0" maximum="900.0" minimum="200.0" name="weight" tag="wght">
+ <map input="200" output="0" /> <!-- ExtraLight -->
+ <map input="300" output="100" /> <!-- Light -->
+ <map input="400" output="368" /> <!-- Regular -->
+ <map input="500" output="486" /> <!-- Medium -->
+ <map input="600" output="600" /> <!-- Semibold -->
+ <map input="700" output="824" /> <!-- Bold -->
+ <map input="900" output="1000" /><!-- Black -->
+ </axis>
+ </axes>
+ <rules>
+ <rule name="named.rule.1">
+ <conditionset>
+ <condition maximum="600" minimum="0" name="weight" />
+ </conditionset>
+ <sub name="dollar" with="dollar.a" />
+ </rule>
+ </rules>
+ <sources>
+ <source filename="master_cff2_input/TestCFF2_ExtraLight.ufo" name="master_0">
+ <lib copy="1" />
+ <location>
+ <dimension name="weight" xvalue="0" />
+ </location>
+ </source>
+ <source filename="master_cff2_input/TestCFF2_Regular.ufo" name="master_1">
+ <glyph mute="1" name="T" />
+ <info copy="1" />
+ <location>
+ <dimension name="weight" xvalue="368" />
+ </location>
+ </source>
+ <source filename="master_cff2_input/TestCFF2_Black.ufo" name="master_2">
+ <location>
+ <dimension name="weight" xvalue="1000" />
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-ExtraLight" stylename="ExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Light" stylename="Light">
+ <location>
+ <dimension name="weight" xvalue="100" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Regular" stylename="Regular">
+ <location>
+ <dimension name="weight" xvalue="368" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Medium" stylename="Medium">
+ <location>
+ <dimension name="weight" xvalue="486" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Semibold" stylename="Semibold">
+ <location>
+ <dimension name="weight" xvalue="600" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Bold" stylename="Bold">
+ <location>
+ <dimension name="weight" xvalue="824" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Black" stylename="Black">
+ <location>
+ <dimension name="weight" xvalue="1000" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/varLib/data/VarLibLocationTest.designspace b/Tests/varLib/data/VarLibLocationTest.designspace
new file mode 100644
index 00000000..b73e1b50
--- /dev/null
+++ b/Tests/varLib/data/VarLibLocationTest.designspace
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='utf-8'?>
+<designspace format="4.0">
+ <axes>
+ <axis default="100" maximum="900" minimum="100" name="weight" tag="wght">
+ <map input="500" output="105"/>
+ <map input="300" output="57"/>
+ <map input="900" output="158"/>
+ <map input="100" output="0"/>
+ </axis>
+ <axis default="50" maximum="100" minimum="0" name="width" tag="wdth" />
+ </axes>
+ <sources>
+ <source filename="A.ufo">
+ <location>
+ <dimension name="weight" xvalue="0" />
+ <dimension name="width" xvalue="50" />
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance filename="C.ufo" familyname="C" stylename="CCC">
+ <location>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/varLib/data/master_base_test/TestBASE.0.ttx b/Tests/varLib/data/master_base_test/TestBASE.0.ttx
new file mode 100644
index 00000000..07f827f5
--- /dev/null
+++ b/Tests/varLib/data/master_base_test/TestBASE.0.ttx
@@ -0,0 +1,700 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.4">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="space"/>
+ <GlyphID id="2" name="A"/>
+ <GlyphID id="3" name="uni5B89"/>
+ <GlyphID id="4" name="uni304B"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0xf10ba20c"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Mar 19 17:38:24 2020"/>
+ <modified value="Thu Mar 19 12:05:00 2020"/>
+ <xMin value="-2"/>
+ <yMin value="-200"/>
+ <xMax value="801"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1096"/>
+ <descent value="-161"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="1000"/>
+ <minLeftSideBearing value="-2"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="801"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="4"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="5"/>
+ <maxPoints value="73"/>
+ <maxContours value="10"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="2"/>
+ <maxStorage value="30"/>
+ <maxFunctionDefs value="6"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="100"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="740"/>
+ <usWeightClass value="100"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="2"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="23433"/>
+ <sTypoAscender value="1096"/>
+ <sTypoDescender value="-161"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="1096"/>
+ <usWinDescent value="161"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="A" width="600" lsb="-2"/>
+ <mtx name="space" width="600" lsb="0"/>
+ <mtx name="uni304B" width="1000" lsb="199"/>
+ <mtx name="uni5B89" width="1000" lsb="198"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x304b" name="uni304B"/><!-- HIRAGANA LETTER KA -->
+ <map code="0x5b89" name="uni5B89"/><!-- CJK UNIFIED IDEOGRAPH-5B89 -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x304b" name="uni304B"/><!-- HIRAGANA LETTER KA -->
+ <map code="0x5b89" name="uni5B89"/><!-- CJK UNIFIED IDEOGRAPH-5B89 -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="93" yMin="-200" xMax="410" yMax="800">
+ <contour>
+ <pt x="410" y="800" on="1"/>
+ <pt x="410" y="-200" on="1"/>
+ <pt x="93" y="-200" on="1"/>
+ <pt x="93" y="800" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="733" on="1"/>
+ <pt x="168" y="733" on="1"/>
+ <pt x="168" y="700" on="1"/>
+ <pt x="233" y="700" on="1"/>
+ <pt x="233" y="663" on="1"/>
+ <pt x="167" y="663" on="1"/>
+ <pt x="167" y="630" on="1"/>
+ <pt x="333" y="630" on="1"/>
+ <pt x="333" y="663" on="1"/>
+ <pt x="267" y="663" on="1"/>
+ <pt x="267" y="700" on="1"/>
+ <pt x="333" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="267" y="604" on="1"/>
+ <pt x="167" y="604" on="1"/>
+ <pt x="167" y="500" on="1"/>
+ <pt x="333" y="500" on="1"/>
+ <pt x="333" y="534" on="1"/>
+ <pt x="267" y="534" on="1"/>
+ </contour>
+ <contour>
+ <pt x="233" y="570" on="1"/>
+ <pt x="233" y="534" on="1"/>
+ <pt x="200" y="534" on="1"/>
+ <pt x="200" y="570" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="473" on="1"/>
+ <pt x="167" y="473" on="1"/>
+ <pt x="167" y="440" on="1"/>
+ <pt x="233" y="440" on="1"/>
+ <pt x="233" y="403" on="1"/>
+ <pt x="167" y="403" on="1"/>
+ <pt x="167" y="370" on="1"/>
+ <pt x="267" y="370" on="1"/>
+ <pt x="267" y="440" on="1"/>
+ <pt x="333" y="440" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="413" on="1"/>
+ <pt x="300" y="413" on="1"/>
+ <pt x="300" y="347" on="1"/>
+ <pt x="167" y="347" on="1"/>
+ <pt x="167" y="313" on="1"/>
+ <pt x="333" y="313" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="291" on="1"/>
+ <pt x="233" y="291" on="1"/>
+ <pt x="233" y="235" on="1"/>
+ <pt x="267" y="235" on="1"/>
+ <pt x="267" y="258" on="1"/>
+ <pt x="300" y="258" on="1"/>
+ <pt x="300" y="211" on="1"/>
+ <pt x="200" y="211" on="1"/>
+ <pt x="200" y="291" on="1"/>
+ <pt x="167" y="291" on="1"/>
+ <pt x="167" y="178" on="1"/>
+ <pt x="333" y="178" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="118" on="1"/>
+ <pt x="167" y="118" on="1"/>
+ <pt x="167" y="5" on="1"/>
+ <pt x="333" y="5" on="1"/>
+ </contour>
+ <contour>
+ <pt x="300" y="85" on="1"/>
+ <pt x="300" y="38" on="1"/>
+ <pt x="200" y="38" on="1"/>
+ <pt x="200" y="85" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="-18" on="1"/>
+ <pt x="167" y="-18" on="1"/>
+ <pt x="167" y="-51" on="1"/>
+ <pt x="237" y="-51" on="1"/>
+ <pt x="167" y="-98" on="1"/>
+ <pt x="167" y="-131" on="1"/>
+ <pt x="333" y="-131" on="1"/>
+ <pt x="333" y="-98" on="1"/>
+ <pt x="231" y="-98" on="1"/>
+ <pt x="301" y="-51" on="1"/>
+ <pt x="333" y="-51" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="-2" yMin="0" xMax="600" yMax="700">
+ <contour>
+ <pt x="-2" y="700" on="1"/>
+ <pt x="600" y="700" on="1"/>
+ <pt x="600" y="0" on="1"/>
+ <pt x="-2" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ <TTGlyph name="uni304B" xMin="199" yMin="-20" xMax="801" yMax="630">
+ <contour>
+ <pt x="199" y="630" on="1"/>
+ <pt x="801" y="630" on="1"/>
+ <pt x="801" y="-20" on="1"/>
+ <pt x="199" y="-20" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="uni5B89" xMin="198" yMin="-51" xMax="800" yMax="649">
+ <contour>
+ <pt x="198" y="649" on="1"/>
+ <pt x="800" y="649" on="1"/>
+ <pt x="800" y="-51" on="1"/>
+ <pt x="198" y="-51" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ TestBASE
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Thin
+ </namerecord>
+ <namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ 1.000;UKWN;TestBASE-Thin
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ TestBASE Thin
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ TestBASE-Thin
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ TestBASE Thin
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;UKWN;TestBASE-Thin
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ TestBASE Thin
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ TestBASE-Thin
+ </namerecord>
+ <namerecord nameID="16" platformID="3" platEncID="1" langID="0x409">
+ TestBASE
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Thin
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="uni5B89"/>
+ <psName name="uni304B"/>
+ </extraNames>
+ </post>
+
+ <BASE>
+ <Version value="0x00010000"/>
+ <HorizAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=5 -->
+ <BaselineTag index="0" value="icfb"/>
+ <BaselineTag index="1" value="icft"/>
+ <BaselineTag index="2" value="ideo"/>
+ <BaselineTag index="3" value="idtp"/>
+ <BaselineTag index="4" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=6 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-75"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="835"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-75"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="835"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-75"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="835"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="hani"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-75"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="835"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="4">
+ <BaseScriptTag value="kana"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-75"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="835"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="5">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-75"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="835"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </HorizAxis>
+ <VertAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=5 -->
+ <BaselineTag index="0" value="icfb"/>
+ <BaselineTag index="1" value="icft"/>
+ <BaselineTag index="2" value="ideo"/>
+ <BaselineTag index="3" value="idtp"/>
+ <BaselineTag index="4" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=6 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="45"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="955"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="45"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="955"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="45"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="955"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="hani"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="45"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="955"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="4">
+ <BaseScriptTag value="kana"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="45"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="955"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="5">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="45"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="955"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </VertAxis>
+ </BASE>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ </GSUB>
+
+ <DSIG>
+ <!-- note that the Digital Signature will be invalid after recompilation! -->
+ <tableHeader flag="0x0" numSigs="0" version="1"/>
+ </DSIG>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_base_test/TestBASE.900.ttx b/Tests/varLib/data/master_base_test/TestBASE.900.ttx
new file mode 100644
index 00000000..8b27fb69
--- /dev/null
+++ b/Tests/varLib/data/master_base_test/TestBASE.900.ttx
@@ -0,0 +1,700 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.4">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="space"/>
+ <GlyphID id="2" name="A"/>
+ <GlyphID id="3" name="uni5B89"/>
+ <GlyphID id="4" name="uni304B"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0xc8faf2b8"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Mar 19 17:38:24 2020"/>
+ <modified value="Thu Mar 19 12:04:56 2020"/>
+ <xMin value="-2"/>
+ <yMin value="-200"/>
+ <xMax value="801"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="1000"/>
+ <minLeftSideBearing value="-2"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="801"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="4"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="5"/>
+ <maxPoints value="73"/>
+ <maxContours value="10"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="2"/>
+ <maxStorage value="30"/>
+ <maxFunctionDefs value="6"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="100"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="740"/>
+ <usWeightClass value="900"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="10"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="23433"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="A" width="600" lsb="-2"/>
+ <mtx name="space" width="600" lsb="0"/>
+ <mtx name="uni304B" width="1000" lsb="199"/>
+ <mtx name="uni5B89" width="1000" lsb="198"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x304b" name="uni304B"/><!-- HIRAGANA LETTER KA -->
+ <map code="0x5b89" name="uni5B89"/><!-- CJK UNIFIED IDEOGRAPH-5B89 -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x304b" name="uni304B"/><!-- HIRAGANA LETTER KA -->
+ <map code="0x5b89" name="uni5B89"/><!-- CJK UNIFIED IDEOGRAPH-5B89 -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="93" yMin="-200" xMax="410" yMax="800">
+ <contour>
+ <pt x="410" y="800" on="1"/>
+ <pt x="410" y="-200" on="1"/>
+ <pt x="93" y="-200" on="1"/>
+ <pt x="93" y="800" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="733" on="1"/>
+ <pt x="168" y="733" on="1"/>
+ <pt x="168" y="700" on="1"/>
+ <pt x="233" y="700" on="1"/>
+ <pt x="233" y="663" on="1"/>
+ <pt x="167" y="663" on="1"/>
+ <pt x="167" y="630" on="1"/>
+ <pt x="333" y="630" on="1"/>
+ <pt x="333" y="663" on="1"/>
+ <pt x="267" y="663" on="1"/>
+ <pt x="267" y="700" on="1"/>
+ <pt x="333" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="267" y="604" on="1"/>
+ <pt x="167" y="604" on="1"/>
+ <pt x="167" y="500" on="1"/>
+ <pt x="333" y="500" on="1"/>
+ <pt x="333" y="534" on="1"/>
+ <pt x="267" y="534" on="1"/>
+ </contour>
+ <contour>
+ <pt x="233" y="570" on="1"/>
+ <pt x="233" y="534" on="1"/>
+ <pt x="200" y="534" on="1"/>
+ <pt x="200" y="570" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="473" on="1"/>
+ <pt x="167" y="473" on="1"/>
+ <pt x="167" y="440" on="1"/>
+ <pt x="233" y="440" on="1"/>
+ <pt x="233" y="403" on="1"/>
+ <pt x="167" y="403" on="1"/>
+ <pt x="167" y="370" on="1"/>
+ <pt x="267" y="370" on="1"/>
+ <pt x="267" y="440" on="1"/>
+ <pt x="333" y="440" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="413" on="1"/>
+ <pt x="300" y="413" on="1"/>
+ <pt x="300" y="347" on="1"/>
+ <pt x="167" y="347" on="1"/>
+ <pt x="167" y="313" on="1"/>
+ <pt x="333" y="313" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="291" on="1"/>
+ <pt x="233" y="291" on="1"/>
+ <pt x="233" y="235" on="1"/>
+ <pt x="267" y="235" on="1"/>
+ <pt x="267" y="258" on="1"/>
+ <pt x="300" y="258" on="1"/>
+ <pt x="300" y="211" on="1"/>
+ <pt x="200" y="211" on="1"/>
+ <pt x="200" y="291" on="1"/>
+ <pt x="167" y="291" on="1"/>
+ <pt x="167" y="178" on="1"/>
+ <pt x="333" y="178" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="118" on="1"/>
+ <pt x="167" y="118" on="1"/>
+ <pt x="167" y="5" on="1"/>
+ <pt x="333" y="5" on="1"/>
+ </contour>
+ <contour>
+ <pt x="300" y="85" on="1"/>
+ <pt x="300" y="38" on="1"/>
+ <pt x="200" y="38" on="1"/>
+ <pt x="200" y="85" on="1"/>
+ </contour>
+ <contour>
+ <pt x="333" y="-18" on="1"/>
+ <pt x="167" y="-18" on="1"/>
+ <pt x="167" y="-51" on="1"/>
+ <pt x="237" y="-51" on="1"/>
+ <pt x="167" y="-98" on="1"/>
+ <pt x="167" y="-131" on="1"/>
+ <pt x="333" y="-131" on="1"/>
+ <pt x="333" y="-98" on="1"/>
+ <pt x="231" y="-98" on="1"/>
+ <pt x="301" y="-51" on="1"/>
+ <pt x="333" y="-51" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="-2" yMin="0" xMax="600" yMax="700">
+ <contour>
+ <pt x="-2" y="700" on="1"/>
+ <pt x="600" y="700" on="1"/>
+ <pt x="600" y="0" on="1"/>
+ <pt x="-2" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ <TTGlyph name="uni304B" xMin="199" yMin="-20" xMax="801" yMax="630">
+ <contour>
+ <pt x="199" y="630" on="1"/>
+ <pt x="801" y="630" on="1"/>
+ <pt x="801" y="-20" on="1"/>
+ <pt x="199" y="-20" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="uni5B89" xMin="198" yMin="-51" xMax="800" yMax="649">
+ <contour>
+ <pt x="198" y="649" on="1"/>
+ <pt x="800" y="649" on="1"/>
+ <pt x="800" y="-51" on="1"/>
+ <pt x="198" y="-51" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ TestBASE
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Black
+ </namerecord>
+ <namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ 1.000;UKWN;TestBASE-Black
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ TestBASE Black
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ TestBASE-Black
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ TestBASE Black
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;UKWN;TestBASE-Black
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ TestBASE Black
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ TestBASE-Black
+ </namerecord>
+ <namerecord nameID="16" platformID="3" platEncID="1" langID="0x409">
+ TestBASE
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Black
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="uni5B89"/>
+ <psName name="uni304B"/>
+ </extraNames>
+ </post>
+
+ <BASE>
+ <Version value="0x00010000"/>
+ <HorizAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=5 -->
+ <BaselineTag index="0" value="icfb"/>
+ <BaselineTag index="1" value="icft"/>
+ <BaselineTag index="2" value="ideo"/>
+ <BaselineTag index="3" value="idtp"/>
+ <BaselineTag index="4" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=6 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-92"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="852"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-92"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="852"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-92"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="852"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="hani"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-92"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="852"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="4">
+ <BaseScriptTag value="kana"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-92"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="852"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="5">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-92"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="852"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </HorizAxis>
+ <VertAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=5 -->
+ <BaselineTag index="0" value="icfb"/>
+ <BaselineTag index="1" value="icft"/>
+ <BaselineTag index="2" value="ideo"/>
+ <BaselineTag index="3" value="idtp"/>
+ <BaselineTag index="4" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=6 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="28"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="972"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="28"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="972"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="28"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="972"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="hani"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="28"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="972"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="4">
+ <BaseScriptTag value="kana"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="28"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="972"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="5">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="28"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="972"/>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </VertAxis>
+ </BASE>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ </GSUB>
+
+ <DSIG>
+ <!-- note that the Digital Signature will be invalid after recompilation! -->
+ <tableHeader flag="0x0" numSigs="0" version="1"/>
+ </DSIG>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_cff2/TestCFF2_Black.ttx b/Tests/varLib/data/master_cff2/TestCFF2_Black.ttx
index 7270a161..8b633f1a 100644
--- a/Tests/varLib/data/master_cff2/TestCFF2_Black.ttx
+++ b/Tests/varLib/data/master_cff2/TestCFF2_Black.ttx
@@ -495,7 +495,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="dollar" out="dollar.a"/>
</SingleSubst>
</Lookup>
diff --git a/Tests/varLib/data/master_cff2/TestCFF2_ExtraLight.ttx b/Tests/varLib/data/master_cff2/TestCFF2_ExtraLight.ttx
index 41dbb750..aae43aac 100644
--- a/Tests/varLib/data/master_cff2/TestCFF2_ExtraLight.ttx
+++ b/Tests/varLib/data/master_cff2/TestCFF2_ExtraLight.ttx
@@ -495,7 +495,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="dollar" out="dollar.a"/>
</SingleSubst>
</Lookup>
diff --git a/Tests/varLib/data/master_cff2/TestCFF2_Regular.ttx b/Tests/varLib/data/master_cff2/TestCFF2_Regular.ttx
index 49d116ce..471eb248 100644
--- a/Tests/varLib/data/master_cff2/TestCFF2_Regular.ttx
+++ b/Tests/varLib/data/master_cff2/TestCFF2_Regular.ttx
@@ -493,7 +493,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="dollar" out="dollar.a"/>
</SingleSubst>
</Lookup>
diff --git a/Tests/varLib/data/master_cff2_input/TestCFF2_Black.ttx b/Tests/varLib/data/master_cff2_input/TestCFF2_Black.ttx
new file mode 100644
index 00000000..3280eeac
--- /dev/null
+++ b/Tests/varLib/data/master_cff2_input/TestCFF2_Black.ttx
@@ -0,0 +1,510 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.2">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="T"/>
+ <GlyphID id="3" name="dollar.a"/>
+ <GlyphID id="4" name="dollar"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.01"/>
+ <checkSumAdjustment value="0x26378952"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Nov 29 14:52:09 2018"/>
+ <modified value="Thu Nov 29 14:52:09 2018"/>
+ <xMin value="0"/>
+ <yMin value="-116"/>
+ <xMax value="600"/>
+ <yMax value="750"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="984"/>
+ <descent value="-273"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="600"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="5"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="5"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="592"/>
+ <usWeightClass value="900"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="11"/>
+ <bWeight value="8"/>
+ <bProportion value="9"/>
+ <bContrast value="3"/>
+ <bStrokeVariation value="4"/>
+ <bArmStyle value="3"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="ADBO"/>
+ <fsSelection value="00000000 00000000"/>
+ <usFirstCharIndex value="36"/>
+ <usLastCharIndex value="84"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="984"/>
+ <usWinDescent value="273"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="660"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="1"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ 1.010;ADBO;SourceCode_Black
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SourceCode_Black
+ </namerecord>
+ <namerecord nameID="17" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Roman Master 2
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.010;ADBO;SourceCode_Black
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SourceCode_Black
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Roman Master 2
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ <cmap_format_6 platformID="1" platEncID="0" language="0">
+ <map code="0x24" name="dollar"/>
+ <map code="0x41" name="A"/>
+ <map code="0x54" name="T"/>
+ </cmap_format_6>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ </cmap>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="1"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.a"/>
+ </extraNames>
+ </post>
+
+ <CFF2>
+ <major value="2"/>
+ <minor value="0"/>
+ <CFFFont name="CFF2Font">
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FDArray>
+ <FontDict index="0">
+ <Private>
+ <BlueValues value="-12 0 500 512 580 592 634 646 650 662 696 708"/>
+ <OtherBlues value="-188 -176"/>
+ <FamilyBlues value="-12 0 486 498 574 586 638 650 656 668 712 724"/>
+ <FamilyOtherBlues value="-217 -205"/>
+ <BlueScale value="0.0625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="0"/>
+ <StdHW value="134"/>
+ <StdVW value="172"/>
+ </Private>
+ </FontDict>
+ </FDArray>
+ <CharStrings>
+ <CharString name=".notdef">
+ 24 0 rmoveto
+ 552 0 rlineto
+ 0 660 rlineto
+ -552 0 rlineto
+ 0 -660 rlineto
+ 212 104 rmoveto
+ 26 56 rlineto
+ 36 96 rlineto
+ 4 0 rlineto
+ 36 -96 rlineto
+ 26 -56 rlineto
+ -128 0 rlineto
+ -100 68 rmoveto
+ 0 336 rlineto
+ 82 -168 rlineto
+ -82 -168 rlineto
+ 162 252 rmoveto
+ -40 96 rlineto
+ -18 36 rlineto
+ 120 0 rlineto
+ -18 -36 rlineto
+ -40 -96 rlineto
+ -4 0 rlineto
+ 84 -84 rmoveto
+ 82 168 rlineto
+ 0 -336 rlineto
+ -82 168 rlineto
+ </CharString>
+ <CharString name="A">
+ 0 0 rmoveto
+ 176 0 rlineto
+ 73 316 rlineto
+ 14 62 17 78 14 66 rrcurveto
+ 4 0 rlineto
+ 14 -66 19 -78 14 -62 rrcurveto
+ 73 -316 rlineto
+ 182 0 rlineto
+ -196 650 rlineto
+ -208 0 rlineto
+ -196 -650 rlineto
+ 141 138 rmoveto
+ 316 0 rlineto
+ 0 133 rlineto
+ -316 0 rlineto
+ 0 -133 rlineto
+ </CharString>
+ <CharString name="T">
+ 214 0 rmoveto
+ 172 0 rlineto
+ 0 506 rlineto
+ 187 0 rlineto
+ 0 144 rlineto
+ -546 0 rlineto
+ 0 -144 rlineto
+ 187 0 rlineto
+ 0 -506 rlineto
+ </CharString>
+ <CharString name="dollar">
+ -107 260 39 rmoveto
+ -65 0 -28 11 -49 24 rrcurveto
+ 89 -53 rlineto
+ -15 89 rlineto
+ -9 52 -22 18 -43 0 rrcurveto
+ -26 0 -27 -14 -14 -38 rrcurveto
+ 0 -90 71 -54 139 0 rrcurveto
+ 163 0 99 84 0 117 rrcurveto
+ 0 98 -58 68 -142 45 rrcurveto
+ -33 10 rlineto
+ -72 22 -24 24 0 49 rrcurveto
+ 0 61 47 23 67 0 rrcurveto
+ 42 0 27 -4 52 -24 rrcurveto
+ -85 47 rlineto
+ 10 -67 rlineto
+ 11 -75 37 -14 39 0 rrcurveto
+ 26 0 29 15 5 41 rrcurveto
+ -8 88 -76 48 -121 0 rrcurveto
+ -158 0 -85 -80 0 -115 rrcurveto
+ 0 -93 66 -69 121 -39 rrcurveto
+ 32 -11 rlineto
+ 80 -28 23 -19 0 -53 rrcurveto
+ 0 -55 -43 -39 -72 0 rrcurveto
+ 64 275 rmoveto
+ 0 417 rlineto
+ -71 0 rlineto
+ 0 -417 rlineto
+ 71 0 rlineto
+ -79 -429 rmoveto
+ 71 0 rlineto
+ 0 429 rlineto
+ -71 0 rlineto
+ 0 -429 rlineto
+ </CharString>
+ <CharString name="dollar.a">
+ 292 34 rmoveto
+ 163 0 83 80 0 100 rrcurveto
+ 0 182 -302 -4 0 56 rrcurveto
+ 0 21 18 11 36 0 rrcurveto
+ 55 0 39 -16 52 -32 rrcurveto
+ 84 98 rlineto
+ -53 52 -69 36 -97 0 rrcurveto
+ -141 0 -88 -68 0 -104 rrcurveto
+ 0 -188 302 12 0 -68 rrcurveto
+ 0 -20 -19 -10 -37 0 rrcurveto
+ -61 0 -55 20 -72 40 rrcurveto
+ -74 -116 rlineto
+ 65 -54 101 -28 70 0 rrcurveto
+ -19 -150 rmoveto
+ 160 854 rlineto
+ -100 12 rlineto
+ -160 -854 rlineto
+ 100 -12 rlineto
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF2>
+
+ <BASE>
+ <Version value="0x00010000"/>
+ <HorizAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=2 -->
+ <BaselineTag index="0" value="ideo"/>
+ <BaselineTag index="1" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=4 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </HorizAxis>
+ </BASE>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="size"/>
+ <Feature>
+ <FeatureParamsSize>
+ <DesignSize value="10.0"/>
+ <SubfamilyID value="0"/>
+ <SubfamilyNameID value="0"/>
+ <RangeStart value="0.0"/>
+ <RangeEnd value="0.0"/>
+ </FeatureParamsSize>
+ <!-- LookupCount=0 -->
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=0 -->
+ </LookupList>
+ </GPOS>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="dollar" out="dollar.a"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+ <hmtx>
+ <mtx name=".notdef" width="600" lsb="24"/>
+ <mtx name="A" width="600" lsb="0"/>
+ <mtx name="T" width="600" lsb="27"/>
+ <mtx name="dollar" width="560" lsb="51"/>
+ <mtx name="dollar.a" width="600" lsb="56"/>
+ </hmtx>
+
+ <DSIG>
+ <!-- note that the Digital Signature will be invalid after recompilation! -->
+ <tableHeader flag="0x0" numSigs="0" version="1"/>
+ </DSIG>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_cff2_input/TestCFF2_ExtraLight.ttx b/Tests/varLib/data/master_cff2_input/TestCFF2_ExtraLight.ttx
new file mode 100644
index 00000000..fbcf91a8
--- /dev/null
+++ b/Tests/varLib/data/master_cff2_input/TestCFF2_ExtraLight.ttx
@@ -0,0 +1,510 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.2">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="T"/>
+ <GlyphID id="3" name="dollar.a"/>
+ <GlyphID id="4" name="dollar"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.01"/>
+ <checkSumAdjustment value="0xeb345d38"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Nov 29 14:52:09 2018"/>
+ <modified value="Thu Nov 29 14:52:09 2018"/>
+ <xMin value="50"/>
+ <yMin value="-115"/>
+ <xMax value="550"/>
+ <yMax value="762"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="984"/>
+ <descent value="-273"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="50"/>
+ <minRightSideBearing value="50"/>
+ <xMaxExtent value="550"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="5"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="5"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="578"/>
+ <usWeightClass value="200"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="286"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="11"/>
+ <bWeight value="3"/>
+ <bProportion value="9"/>
+ <bContrast value="3"/>
+ <bStrokeVariation value="4"/>
+ <bArmStyle value="3"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="ADBO"/>
+ <fsSelection value="00000000 00000000"/>
+ <usFirstCharIndex value="36"/>
+ <usLastCharIndex value="84"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="984"/>
+ <usWinDescent value="273"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="478"/>
+ <sCapHeight value="660"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="1"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ 1.010;ADBO;SourceCode_ExtraLight
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SourceCode_ExtraLight
+ </namerecord>
+ <namerecord nameID="17" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Roman Master 0
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.010;ADBO;SourceCode_ExtraLight
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SourceCode_ExtraLight
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Roman Master 0
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ <cmap_format_6 platformID="1" platEncID="0" language="0">
+ <map code="0x24" name="dollar"/>
+ <map code="0x41" name="A"/>
+ <map code="0x54" name="T"/>
+ </cmap_format_6>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ </cmap>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="1"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.a"/>
+ </extraNames>
+ </post>
+
+ <CFF2>
+ <major value="2"/>
+ <minor value="0"/>
+ <CFFFont name="CFF2Font">
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FDArray>
+ <FontDict index="0">
+ <Private>
+ <BlueValues value="-12 0 478 490 570 582 640 652 660 672 722 734"/>
+ <OtherBlues value="-234 -222"/>
+ <FamilyBlues value="-12 0 486 498 574 586 638 650 656 668 712 724"/>
+ <FamilyOtherBlues value="-217 -205"/>
+ <BlueScale value="0.0625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="0"/>
+ <StdHW value="28"/>
+ <StdVW value="34"/>
+ </Private>
+ </FontDict>
+ </FDArray>
+ <CharStrings>
+ <CharString name=".notdef">
+ 84 0 rmoveto
+ 432 0 rlineto
+ 0 660 rlineto
+ -432 0 rlineto
+ 0 -660 rlineto
+ 48 32 rmoveto
+ 102 176 rlineto
+ 64 106 rlineto
+ 4 0 rlineto
+ 62 -106 rlineto
+ 100 -176 rlineto
+ -332 0 rlineto
+ -10 42 rmoveto
+ 0 536 rlineto
+ 154 -270 rlineto
+ -154 -266 rlineto
+ 176 292 rmoveto
+ -56 92 rlineto
+ -94 168 rlineto
+ 302 0 rlineto
+ -94 -168 rlineto
+ -54 -92 rlineto
+ -4 0 rlineto
+ 26 -26 rmoveto
+ 152 270 rlineto
+ 0 -536 rlineto
+ -152 266 rlineto
+ </CharString>
+ <CharString name="A">
+ 50 0 rmoveto
+ 32 0 rlineto
+ 140 396 rlineto
+ 28 80 24 68 24 82 rrcurveto
+ 4 0 rlineto
+ 24 -82 24 -68 28 -80 rrcurveto
+ 138 -396 rlineto
+ 34 0 rlineto
+ -236 660 rlineto
+ -28 0 rlineto
+ -236 -660 rlineto
+ 102 236 rmoveto
+ 293 0 rlineto
+ 0 28 rlineto
+ -293 0 rlineto
+ 0 -28 rlineto
+ </CharString>
+ <CharString name="T">
+ 284 0 rmoveto
+ 32 0 rlineto
+ 0 632 rlineto
+ 234 0 rlineto
+ 0 28 rlineto
+ -500 0 rlineto
+ 0 -28 rlineto
+ 234 0 rlineto
+ 0 -632 rlineto
+ </CharString>
+ <CharString name="dollar">
+ -107 245 7 rmoveto
+ -65 0 -39 15 -46 50 rrcurveto
+ 36 -48 rlineto
+ -28 100 rlineto
+ -4 16 -12 4 -11 0 rrcurveto
+ -14 0 -8 -7 -1 -14 rrcurveto
+ 24 -85 61 -51 107 0 rrcurveto
+ 91 0 90 53 0 111 rrcurveto
+ 0 70 -26 66 -134 57 rrcurveto
+ -19 8 rlineto
+ -93 39 -42 49 0 68 rrcurveto
+ 0 91 60 48 88 0 rrcurveto
+ 56 0 35 -14 44 -50 rrcurveto
+ -38 47 rlineto
+ 28 -100 rlineto
+ 6 -15 10 -5 11 0 rrcurveto
+ 14 0 8 7 1 14 rrcurveto
+ -24 88 -67 48 -84 0 rrcurveto
+ -92 0 -82 -51 0 -108 rrcurveto
+ 0 -80 45 -53 92 -42 rrcurveto
+ 37 -17 rlineto
+ 114 -52 26 -46 0 -65 rrcurveto
+ 0 -92 -65 -54 -90 0 rrcurveto
+ 18 327 rmoveto
+ 0 428 rlineto
+ -22 0 rlineto
+ 0 -428 rlineto
+ 22 0 rlineto
+ -22 -449 rmoveto
+ 22 0 rlineto
+ 0 449 rlineto
+ -22 0 rlineto
+ 0 -449 rlineto
+ </CharString>
+ <CharString name="dollar.a">
+ 311 34 rmoveto
+ 103 0 88 56 0 94 rrcurveto
+ 0 184 -338 -32 0 142 rrcurveto
+ 0 68 57 44 85 0 rrcurveto
+ 76 0 34 -24 44 -38 rrcurveto
+ 20 20 rlineto
+ -41 38 -45 32 -85 0 rrcurveto
+ -99 0 -78 -54 0 -88 rrcurveto
+ 0 -166 338 28 0 -156 rrcurveto
+ 0 -70 -56 -50 -103 0 rrcurveto
+ -85 0 -66 38 -40 34 rrcurveto
+ -18 -22 rlineto
+ 45 -38 73 -40 91 0 rrcurveto
+ -70 -146 rmoveto
+ 158 860 rlineto
+ -30 4 rlineto
+ -158 -860 rlineto
+ 30 -4 rlineto
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF2>
+
+ <BASE>
+ <Version value="0x00010000"/>
+ <HorizAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=2 -->
+ <BaselineTag index="0" value="ideo"/>
+ <BaselineTag index="1" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=4 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </HorizAxis>
+ </BASE>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="size"/>
+ <Feature>
+ <FeatureParamsSize>
+ <DesignSize value="10.0"/>
+ <SubfamilyID value="0"/>
+ <SubfamilyNameID value="0"/>
+ <RangeStart value="0.0"/>
+ <RangeEnd value="0.0"/>
+ </FeatureParamsSize>
+ <!-- LookupCount=0 -->
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=0 -->
+ </LookupList>
+ </GPOS>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="dollar" out="dollar.a"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+ <hmtx>
+ <mtx name=".notdef" width="600" lsb="84"/>
+ <mtx name="A" width="600" lsb="50"/>
+ <mtx name="T" width="600" lsb="50"/>
+ <mtx name="dollar" width="490" lsb="53"/>
+ <mtx name="dollar.a" width="600" lsb="102"/>
+ </hmtx>
+
+ <DSIG>
+ <!-- note that the Digital Signature will be invalid after recompilation! -->
+ <tableHeader flag="0x0" numSigs="0" version="1"/>
+ </DSIG>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_cff2_input/TestCFF2_Regular.ttx b/Tests/varLib/data/master_cff2_input/TestCFF2_Regular.ttx
new file mode 100644
index 00000000..757e5b89
--- /dev/null
+++ b/Tests/varLib/data/master_cff2_input/TestCFF2_Regular.ttx
@@ -0,0 +1,508 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.2">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="T"/>
+ <GlyphID id="3" name="dollar.a"/>
+ <GlyphID id="4" name="dollar"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.01"/>
+ <checkSumAdjustment value="0x60d07155"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Nov 29 14:52:09 2018"/>
+ <modified value="Thu Nov 29 14:52:09 2018"/>
+ <xMin value="31"/>
+ <yMin value="-115"/>
+ <xMax value="569"/>
+ <yMax value="751"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="984"/>
+ <descent value="-273"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="31"/>
+ <minRightSideBearing value="31"/>
+ <xMaxExtent value="569"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="5"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="5"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="579"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="291"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="11"/>
+ <bWeight value="5"/>
+ <bProportion value="9"/>
+ <bContrast value="3"/>
+ <bStrokeVariation value="4"/>
+ <bArmStyle value="3"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="ADBO"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="36"/>
+ <usLastCharIndex value="84"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="984"/>
+ <usWinDescent value="273"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="486"/>
+ <sCapHeight value="660"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="1"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ 1.010;ADBO;SourceCodeVariable-Roman
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SourceCodeVariable-Roman
+ </namerecord>
+ <namerecord nameID="17" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Roman
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.010;ADBO;SourceCodeVariable-Roman
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SourceCodeVariable-Roman
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Roman
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ <cmap_format_6 platformID="1" platEncID="0" language="0">
+ <map code="0x24" name="dollar"/>
+ <map code="0x41" name="A"/>
+ <map code="0x54" name="T"/>
+ </cmap_format_6>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ </cmap>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="1"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.a"/>
+ </extraNames>
+ </post>
+
+ <CFF2>
+ <major value="2"/>
+ <minor value="0"/>
+ <CFFFont name="CFF2Font">
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FDArray>
+ <FontDict index="0">
+ <Private>
+ <BlueValues value="-12 0 486 498 574 586 638 650 656 668 712 724"/>
+ <OtherBlues value="-217 -205"/>
+ <BlueScale value="0.0625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="0"/>
+ <StdHW value="67"/>
+ <StdVW value="85"/>
+ </Private>
+ </FontDict>
+ </FDArray>
+ <CharStrings>
+ <CharString name=".notdef">
+ 62 0 rmoveto
+ 476 0 rlineto
+ 0 660 rlineto
+ -476 0 rlineto
+ 0 -660 rlineto
+ 109 59 rmoveto
+ 73 131 rlineto
+ 54 102 rlineto
+ 4 0 rlineto
+ 52 -102 rlineto
+ 73 -131 rlineto
+ -256 0 rlineto
+ -44 52 rmoveto
+ 0 461 rlineto
+ 127 -232 rlineto
+ -127 -229 rlineto
+ 171 277 rmoveto
+ -50 93 rlineto
+ -66 119 rlineto
+ 234 0 rlineto
+ -65 -119 rlineto
+ -49 -93 rlineto
+ -4 0 rlineto
+ 48 -48 rmoveto
+ 126 232 rlineto
+ 0 -461 rlineto
+ -126 229 rlineto
+ </CharString>
+ <CharString name="A">
+ 31 0 rmoveto
+ 86 0 rlineto
+ 115 366 rlineto
+ 23 73 21 72 21 76 rrcurveto
+ 4 0 rlineto
+ 20 -76 22 -72 23 -73 rrcurveto
+ 113 -366 rlineto
+ 90 0 rlineto
+ -221 656 rlineto
+ -96 0 rlineto
+ -221 -656 rlineto
+ 117 199 rmoveto
+ 301 0 rlineto
+ 0 68 rlineto
+ -301 0 rlineto
+ 0 -68 rlineto
+ </CharString>
+ <CharString name="T">
+ 258 0 rmoveto
+ 84 0 rlineto
+ 0 585 rlineto
+ 217 0 rlineto
+ 0 71 rlineto
+ -518 0 rlineto
+ 0 -71 rlineto
+ 217 0 rlineto
+ 0 -585 rlineto
+ </CharString>
+ <CharString name="dollar">
+ -107 248 35 rmoveto
+ -39 0 -45 5 -46 18 rrcurveto
+ 53 -36 rlineto
+ -17 76 rlineto
+ -12 53 -22 13 -24 0 rrcurveto
+ -22 0 -14 -11 -9 -20 rrcurveto
+ 4 -87 81 -59 107 0 rrcurveto
+ 136 0 82 76 0 107 rrcurveto
+ 0 82 -41 65 -135 47 rrcurveto
+ -38 13 rlineto
+ -71 23 -40 35 0 64 rrcurveto
+ 0 75 57 37 74 0 rrcurveto
+ 30 0 36 -5 42 -17 rrcurveto
+ -52 36 rlineto
+ 17 -76 rlineto
+ 12 -52 25 -14 22 0 rrcurveto
+ 19 0 17 10 8 21 rrcurveto
+ -6 86 -80 60 -101 0 rrcurveto
+ -115 0 -83 -80 0 -102 rrcurveto
+ 0 -100 62 -54 105 -37 rrcurveto
+ 37 -13 rlineto
+ 85 -30 36 -30 0 -63 rrcurveto
+ 0 -74 -53 -42 -82 0 rrcurveto
+ 31 287 rmoveto
+ 0 428 rlineto
+ -40 0 rlineto
+ 0 -428 rlineto
+ 40 0 rlineto
+ -41 -437 rmoveto
+ 40 0 rlineto
+ 0 437 rlineto
+ -40 0 rlineto
+ 0 -437 rlineto
+ </CharString>
+ <CharString name="dollar.a">
+ 304 34 rmoveto
+ 125 0 86 65 0 96 rrcurveto
+ 0 183 -324 -21 0 110 rrcurveto
+ 0 50 42 32 67 0 rrcurveto
+ 68 0 36 -21 47 -36 rrcurveto
+ 44 49 rlineto
+ -46 44 -54 33 -89 0 rrcurveto
+ -115 0 -81 -59 0 -94 rrcurveto
+ 0 -174 324 22 0 -124 rrcurveto
+ 0 -51 -42 -35 -78 0 rrcurveto
+ -76 0 -62 31 -52 37 rrcurveto
+ -39 -58 rlineto
+ 52 -43 84 -36 83 0 rrcurveto
+ -51 -147 rmoveto
+ 159 857 rlineto
+ -56 7 rlineto
+ -159 -858 rlineto
+ 56 -6 rlineto
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF2>
+
+ <BASE>
+ <Version value="0x00010000"/>
+ <HorizAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=2 -->
+ <BaselineTag index="0" value="ideo"/>
+ <BaselineTag index="1" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=4 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </HorizAxis>
+ </BASE>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="size"/>
+ <Feature>
+ <FeatureParamsSize>
+ <DesignSize value="10.0"/>
+ <SubfamilyID value="0"/>
+ <SubfamilyNameID value="0"/>
+ <RangeStart value="0.0"/>
+ <RangeEnd value="0.0"/>
+ </FeatureParamsSize>
+ <!-- LookupCount=0 -->
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=0 -->
+ </LookupList>
+ </GPOS>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="dollar" out="dollar.a"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+ <hmtx>
+ <mtx name=".notdef" width="600" lsb="62"/>
+ <mtx name="A" width="600" lsb="31"/>
+ <mtx name="T" width="600" lsb="41"/>
+ <mtx name="dollar" width="497" lsb="51"/>
+ <mtx name="dollar.a" width="600" lsb="85"/>
+ </hmtx>
+
+ <DSIG>
+ <!-- note that the Digital Signature will be invalid after recompilation! -->
+ <tableHeader flag="0x0" numSigs="0" version="1"/>
+ </DSIG>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_incompatible_arrays/IncompatibleArrays-Bold.ttx b/Tests/varLib/data/master_incompatible_arrays/IncompatibleArrays-Bold.ttx
new file mode 100644
index 00000000..c47ecbad
--- /dev/null
+++ b/Tests/varLib/data/master_incompatible_arrays/IncompatibleArrays-Bold.ttx
@@ -0,0 +1,612 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="Aacute"/>
+ <GlyphID id="3" name="O"/>
+ <GlyphID id="4" name="V"/>
+ <GlyphID id="5" name="space"/>
+ <GlyphID id="6" name="dollar"/>
+ <GlyphID id="7" name="dollar.bold"/>
+ <GlyphID id="8" name="acutecomb"/>
+ <GlyphID id="9" name="dollar.BRACKET.500"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x10cb3f3"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Jan 15 14:37:13 2021"/>
+ <modified value="Mon Mar 15 12:57:03 2021"/>
+ <xMin value="-141"/>
+ <yMin value="-200"/>
+ <xMax value="906"/>
+ <yMax value="949"/>
+ <macStyle value="00000000 00000001"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="911"/>
+ <minLeftSideBearing value="-141"/>
+ <minRightSideBearing value="-125"/>
+ <xMaxExtent value="906"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="10"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="10"/>
+ <maxPoints value="52"/>
+ <maxContours value="3"/>
+ <maxCompositePoints value="16"/>
+ <maxCompositeContours value="4"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="2"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="672"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 01000011"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 00100000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="769"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="2"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="911" lsb="5"/>
+ <mtx name="Aacute" width="911" lsb="5"/>
+ <mtx name="O" width="715" lsb="15"/>
+ <mtx name="V" width="911" lsb="5"/>
+ <mtx name="acutecomb" width="0" lsb="-141"/>
+ <mtx name="dollar" width="600" lsb="1"/>
+ <mtx name="dollar.BRACKET.500" width="600" lsb="1"/>
+ <mtx name="dollar.bold" width="600" lsb="1"/>
+ <mtx name="space" width="300" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="5" yMin="0" xMax="906" yMax="700">
+ <contour>
+ <pt x="705" y="0" on="1"/>
+ <pt x="906" y="0" on="1"/>
+ <pt x="556" y="700" on="1"/>
+ <pt x="355" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="5" y="0" on="1"/>
+ <pt x="206" y="0" on="1"/>
+ <pt x="556" y="700" on="1"/>
+ <pt x="355" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="640" y="311" on="1"/>
+ <pt x="190" y="311" on="1"/>
+ <pt x="190" y="191" on="1"/>
+ <pt x="640" y="191" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="Aacute" xMin="5" yMin="0" xMax="906" yMax="949">
+ <component glyphName="A" x="0" y="0" flags="0x204"/>
+ <component glyphName="acutecomb" x="479" y="124" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="O" xMin="15" yMin="-10" xMax="670" yMax="710">
+ <contour>
+ <pt x="342" y="-10" on="1"/>
+ <pt x="172" y="-10" on="0"/>
+ <pt x="15" y="163" on="0"/>
+ <pt x="15" y="350" on="1"/>
+ <pt x="15" y="538" on="0"/>
+ <pt x="172" y="710" on="0"/>
+ <pt x="342" y="710" on="1"/>
+ <pt x="513" y="710" on="0"/>
+ <pt x="670" y="538" on="0"/>
+ <pt x="670" y="350" on="1"/>
+ <pt x="670" y="163" on="0"/>
+ <pt x="513" y="-10" on="0"/>
+ </contour>
+ <contour>
+ <pt x="342" y="153" on="1"/>
+ <pt x="419" y="153" on="0"/>
+ <pt x="490" y="247" on="0"/>
+ <pt x="490" y="350" on="1"/>
+ <pt x="490" y="453" on="0"/>
+ <pt x="419" y="547" on="0"/>
+ <pt x="342" y="547" on="1"/>
+ <pt x="266" y="547" on="0"/>
+ <pt x="195" y="453" on="0"/>
+ <pt x="195" y="350" on="1"/>
+ <pt x="195" y="247" on="0"/>
+ <pt x="266" y="153" on="0"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="V" xMin="5" yMin="0" xMax="906" yMax="700">
+ <contour>
+ <pt x="355" y="0" on="1"/>
+ <pt x="705" y="700" on="1"/>
+ <pt x="906" y="700" on="1"/>
+ <pt x="556" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="355" y="0" on="1"/>
+ <pt x="5" y="700" on="1"/>
+ <pt x="206" y="700" on="1"/>
+ <pt x="556" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="acutecomb" xMin="-141" yMin="630" xMax="125" yMax="825">
+ <contour>
+ <pt x="-118" y="756" on="1"/>
+ <pt x="-141" y="630" on="1"/>
+ <pt x="102" y="699" on="1"/>
+ <pt x="125" y="825" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar" xMin="1" yMin="-98" xMax="595" yMax="789">
+ <contour>
+ <pt x="249" y="789" on="1"/>
+ <pt x="369" y="789" on="1"/>
+ <pt x="369" y="743" on="1"/>
+ <pt x="427" y="735" on="0"/>
+ <pt x="537" y="681" on="0"/>
+ <pt x="590" y="623" on="1"/>
+ <pt x="510" y="515" on="1"/>
+ <pt x="479" y="549" on="0"/>
+ <pt x="411" y="588" on="0"/>
+ <pt x="369" y="595" on="1"/>
+ <pt x="369" y="400" on="1"/>
+ <pt x="476" y="378" on="0"/>
+ <pt x="595" y="278" on="0"/>
+ <pt x="595" y="184" on="1"/>
+ <pt x="595" y="93" on="0"/>
+ <pt x="474" y="-32" on="0"/>
+ <pt x="369" y="-46" on="1"/>
+ <pt x="369" y="-98" on="1"/>
+ <pt x="249" y="-98" on="1"/>
+ <pt x="249" y="-47" on="1"/>
+ <pt x="176" y="-39" on="0"/>
+ <pt x="52" y="17" on="0"/>
+ <pt x="1" y="69" on="1"/>
+ <pt x="80" y="179" on="1"/>
+ <pt x="118" y="144" on="0"/>
+ <pt x="195" y="106" on="0"/>
+ <pt x="249" y="100" on="1"/>
+ <pt x="249" y="273" on="1"/>
+ <pt x="246" y="274" on="1"/>
+ <pt x="144" y="294" on="0"/>
+ <pt x="28" y="405" on="0"/>
+ <pt x="28" y="502" on="1"/>
+ <pt x="28" y="567" on="0"/>
+ <pt x="84" y="667" on="0"/>
+ <pt x="184" y="732" on="0"/>
+ <pt x="249" y="742" on="1"/>
+ </contour>
+ <contour>
+ <pt x="152" y="502" on="1"/>
+ <pt x="152" y="480" on="0"/>
+ <pt x="166" y="453" on="0"/>
+ <pt x="208" y="434" on="0"/>
+ <pt x="249" y="424" on="1"/>
+ <pt x="249" y="595" on="1"/>
+ <pt x="199" y="587" on="0"/>
+ <pt x="152" y="538" on="0"/>
+ </contour>
+ <contour>
+ <pt x="369" y="100" on="1"/>
+ <pt x="426" y="107" on="0"/>
+ <pt x="471" y="150" on="0"/>
+ <pt x="471" y="183" on="1"/>
+ <pt x="471" y="201" on="0"/>
+ <pt x="456" y="225" on="0"/>
+ <pt x="412" y="243" on="0"/>
+ <pt x="369" y="252" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.BRACKET.500" xMin="1" yMin="-98" xMax="595" yMax="789">
+ <contour>
+ <pt x="249" y="789" on="1"/>
+ <pt x="369" y="789" on="1"/>
+ <pt x="369" y="743" on="1"/>
+ <pt x="427" y="735" on="0"/>
+ <pt x="537" y="681" on="0"/>
+ <pt x="590" y="623" on="1"/>
+ <pt x="510" y="515" on="1"/>
+ <pt x="468" y="560" on="0"/>
+ <pt x="374" y="600" on="0"/>
+ <pt x="308" y="600" on="1"/>
+ <pt x="227" y="600" on="0"/>
+ <pt x="152" y="548" on="0"/>
+ <pt x="152" y="502" on="1"/>
+ <pt x="152" y="479" on="0"/>
+ <pt x="168" y="450" on="0"/>
+ <pt x="217" y="431" on="0"/>
+ <pt x="264" y="421" on="1"/>
+ <pt x="363" y="401" on="1"/>
+ <pt x="473" y="379" on="0"/>
+ <pt x="595" y="279" on="0"/>
+ <pt x="595" y="184" on="1"/>
+ <pt x="595" y="93" on="0"/>
+ <pt x="474" y="-32" on="0"/>
+ <pt x="369" y="-46" on="1"/>
+ <pt x="369" y="-98" on="1"/>
+ <pt x="249" y="-98" on="1"/>
+ <pt x="249" y="-47" on="1"/>
+ <pt x="176" y="-39" on="0"/>
+ <pt x="52" y="17" on="0"/>
+ <pt x="1" y="69" on="1"/>
+ <pt x="80" y="179" on="1"/>
+ <pt x="112" y="150" on="0"/>
+ <pt x="176" y="114" on="0"/>
+ <pt x="256" y="97" on="0"/>
+ <pt x="310" y="97" on="1"/>
+ <pt x="402" y="97" on="0"/>
+ <pt x="471" y="143" on="0"/>
+ <pt x="471" y="183" on="1"/>
+ <pt x="471" y="203" on="0"/>
+ <pt x="453" y="228" on="0"/>
+ <pt x="399" y="247" on="0"/>
+ <pt x="345" y="256" on="1"/>
+ <pt x="246" y="274" on="1"/>
+ <pt x="144" y="293" on="0"/>
+ <pt x="28" y="405" on="0"/>
+ <pt x="28" y="502" on="1"/>
+ <pt x="28" y="567" on="0"/>
+ <pt x="84" y="667" on="0"/>
+ <pt x="184" y="732" on="0"/>
+ <pt x="249" y="742" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.bold" xMin="1" yMin="-98" xMax="595" yMax="789">
+ <contour>
+ <pt x="249" y="789" on="1"/>
+ <pt x="369" y="789" on="1"/>
+ <pt x="369" y="743" on="1"/>
+ <pt x="427" y="735" on="0"/>
+ <pt x="537" y="681" on="0"/>
+ <pt x="590" y="623" on="1"/>
+ <pt x="510" y="515" on="1"/>
+ <pt x="468" y="560" on="0"/>
+ <pt x="374" y="600" on="0"/>
+ <pt x="308" y="600" on="1"/>
+ <pt x="227" y="600" on="0"/>
+ <pt x="152" y="548" on="0"/>
+ <pt x="152" y="502" on="1"/>
+ <pt x="152" y="479" on="0"/>
+ <pt x="168" y="450" on="0"/>
+ <pt x="217" y="431" on="0"/>
+ <pt x="264" y="421" on="1"/>
+ <pt x="363" y="401" on="1"/>
+ <pt x="473" y="379" on="0"/>
+ <pt x="595" y="279" on="0"/>
+ <pt x="595" y="184" on="1"/>
+ <pt x="595" y="93" on="0"/>
+ <pt x="474" y="-32" on="0"/>
+ <pt x="369" y="-46" on="1"/>
+ <pt x="369" y="-98" on="1"/>
+ <pt x="249" y="-98" on="1"/>
+ <pt x="249" y="-47" on="1"/>
+ <pt x="176" y="-39" on="0"/>
+ <pt x="52" y="17" on="0"/>
+ <pt x="1" y="69" on="1"/>
+ <pt x="80" y="179" on="1"/>
+ <pt x="112" y="150" on="0"/>
+ <pt x="176" y="114" on="0"/>
+ <pt x="256" y="97" on="0"/>
+ <pt x="310" y="97" on="1"/>
+ <pt x="402" y="97" on="0"/>
+ <pt x="471" y="143" on="0"/>
+ <pt x="471" y="183" on="1"/>
+ <pt x="471" y="203" on="0"/>
+ <pt x="453" y="228" on="0"/>
+ <pt x="399" y="247" on="0"/>
+ <pt x="345" y="256" on="1"/>
+ <pt x="246" y="274" on="1"/>
+ <pt x="144" y="293" on="0"/>
+ <pt x="28" y="405" on="0"/>
+ <pt x="28" y="502" on="1"/>
+ <pt x="28" y="567" on="0"/>
+ <pt x="84" y="667" on="0"/>
+ <pt x="184" y="732" on="0"/>
+ <pt x="249" y="742" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Bold
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;SimpleTwoAxis-Bold
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis Bold
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SimpleTwoAxis-Bold
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.bold"/>
+ <psName name="acutecomb"/>
+ <psName name="dollar.BRACKET.500"/>
+ </extraNames>
+ </post>
+
+ <GDEF>
+ <Version value="0x00010000"/>
+ <GlyphClassDef>
+ <ClassDef glyph="A" class="1"/>
+ <ClassDef glyph="Aacute" class="1"/>
+ <ClassDef glyph="acutecomb" class="3"/>
+ </GlyphClassDef>
+ </GDEF>
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=2 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="kern"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="mark"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ <Glyph value="V"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <!-- PairSetCount=3 -->
+ <PairSet index="0">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="1">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="2">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="O"/>
+ <Value1 XAdvance="-20"/>
+ </PairValueRecord>
+ </PairSet>
+ </PairPos>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="4"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MarkBasePos index="0" Format="1">
+ <MarkCoverage>
+ <Glyph value="acutecomb"/>
+ </MarkCoverage>
+ <BaseCoverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ </BaseCoverage>
+ <!-- ClassCount=1 -->
+ <MarkArray>
+ <!-- MarkCount=1 -->
+ <MarkRecord index="0">
+ <Class value="0"/>
+ <MarkAnchor Format="1">
+ <XCoordinate value="4"/>
+ <YCoordinate value="623"/>
+ </MarkAnchor>
+ </MarkRecord>
+ </MarkArray>
+ <BaseArray>
+ <!-- BaseCount=2 -->
+ <BaseRecord index="0">
+ <BaseAnchor index="0" Format="1">
+ <XCoordinate value="406"/>
+ <YCoordinate value="753"/>
+ </BaseAnchor>
+ </BaseRecord>
+ <BaseRecord index="1">
+ <BaseAnchor index="0" Format="1">
+ <XCoordinate value="406"/>
+ <YCoordinate value="753"/>
+ </BaseAnchor>
+ </BaseRecord>
+ </BaseArray>
+ </MarkBasePos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_incompatible_arrays/IncompatibleArrays-Regular.ttx b/Tests/varLib/data/master_incompatible_arrays/IncompatibleArrays-Regular.ttx
new file mode 100644
index 00000000..cabb69a4
--- /dev/null
+++ b/Tests/varLib/data/master_incompatible_arrays/IncompatibleArrays-Regular.ttx
@@ -0,0 +1,626 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="Aacute"/>
+ <GlyphID id="3" name="O"/>
+ <GlyphID id="4" name="V"/>
+ <GlyphID id="5" name="space"/>
+ <GlyphID id="6" name="dollar"/>
+ <GlyphID id="7" name="dollar.bold"/>
+ <GlyphID id="8" name="acutecomb"/>
+ <GlyphID id="9" name="dollar.BRACKET.500"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x3c7bc79b"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Jan 15 14:37:13 2021"/>
+ <modified value="Mon Mar 15 12:57:03 2021"/>
+ <xMin value="-141"/>
+ <yMin value="-200"/>
+ <xMax value="751"/>
+ <yMax value="915"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="756"/>
+ <minLeftSideBearing value="-141"/>
+ <minRightSideBearing value="-125"/>
+ <xMaxExtent value="751"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="10"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="10"/>
+ <maxPoints value="52"/>
+ <maxContours value="3"/>
+ <maxCompositePoints value="16"/>
+ <maxCompositeContours value="4"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="2"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="604"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 01000011"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="769"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="2"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="756" lsb="5"/>
+ <mtx name="Aacute" width="756" lsb="5"/>
+ <mtx name="O" width="664" lsb="30"/>
+ <mtx name="V" width="756" lsb="5"/>
+ <mtx name="acutecomb" width="0" lsb="-141"/>
+ <mtx name="dollar" width="600" lsb="29"/>
+ <mtx name="dollar.BRACKET.500" width="600" lsb="29"/>
+ <mtx name="dollar.bold" width="600" lsb="29"/>
+ <mtx name="space" width="200" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="5" yMin="0" xMax="751" yMax="700">
+ <contour>
+ <pt x="641" y="0" on="1"/>
+ <pt x="751" y="0" on="1"/>
+ <pt x="433" y="700" on="1"/>
+ <pt x="323" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="5" y="0" on="1"/>
+ <pt x="115" y="0" on="1"/>
+ <pt x="433" y="700" on="1"/>
+ <pt x="323" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="567" y="284" on="1"/>
+ <pt x="152" y="284" on="1"/>
+ <pt x="152" y="204" on="1"/>
+ <pt x="567" y="204" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="Aacute" xMin="5" yMin="0" xMax="751" yMax="915">
+ <component glyphName="A" x="0" y="0" flags="0x204"/>
+ <component glyphName="acutecomb" x="402" y="130" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="O" xMin="30" yMin="-10" xMax="634" yMax="710">
+ <contour>
+ <pt x="332" y="-10" on="1"/>
+ <pt x="181" y="-10" on="0"/>
+ <pt x="30" y="169" on="0"/>
+ <pt x="30" y="350" on="1"/>
+ <pt x="30" y="531" on="0"/>
+ <pt x="181" y="710" on="0"/>
+ <pt x="332" y="710" on="1"/>
+ <pt x="484" y="710" on="0"/>
+ <pt x="634" y="531" on="0"/>
+ <pt x="634" y="350" on="1"/>
+ <pt x="634" y="169" on="0"/>
+ <pt x="484" y="-10" on="0"/>
+ </contour>
+ <contour>
+ <pt x="332" y="74" on="1"/>
+ <pt x="438" y="74" on="0"/>
+ <pt x="544" y="212" on="0"/>
+ <pt x="544" y="350" on="1"/>
+ <pt x="544" y="488" on="0"/>
+ <pt x="438" y="626" on="0"/>
+ <pt x="332" y="626" on="1"/>
+ <pt x="226" y="626" on="0"/>
+ <pt x="120" y="488" on="0"/>
+ <pt x="120" y="350" on="1"/>
+ <pt x="120" y="212" on="0"/>
+ <pt x="226" y="74" on="0"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="V" xMin="5" yMin="0" xMax="751" yMax="700">
+ <contour>
+ <pt x="323" y="0" on="1"/>
+ <pt x="641" y="700" on="1"/>
+ <pt x="751" y="700" on="1"/>
+ <pt x="433" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="323" y="0" on="1"/>
+ <pt x="5" y="700" on="1"/>
+ <pt x="115" y="700" on="1"/>
+ <pt x="433" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="acutecomb" xMin="-141" yMin="630" xMax="125" yMax="785">
+ <contour>
+ <pt x="-118" y="716" on="1"/>
+ <pt x="-141" y="630" on="1"/>
+ <pt x="102" y="699" on="1"/>
+ <pt x="125" y="785" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar" xMin="29" yMin="-68" xMax="580" yMax="759">
+ <contour>
+ <pt x="264" y="759" on="1"/>
+ <pt x="354" y="759" on="1"/>
+ <pt x="354" y="715" on="1"/>
+ <pt x="415" y="709" on="0"/>
+ <pt x="519" y="662" on="0"/>
+ <pt x="562" y="620" on="1"/>
+ <pt x="509" y="548" on="1"/>
+ <pt x="473" y="584" on="0"/>
+ <pt x="398" y="621" on="0"/>
+ <pt x="354" y="627" on="1"/>
+ <pt x="354" y="373" on="1"/>
+ <pt x="467" y="351" on="0"/>
+ <pt x="580" y="263" on="0"/>
+ <pt x="580" y="184" on="1"/>
+ <pt x="580" y="102" on="0"/>
+ <pt x="459" y="-8" on="0"/>
+ <pt x="354" y="-18" on="1"/>
+ <pt x="354" y="-68" on="1"/>
+ <pt x="264" y="-68" on="1"/>
+ <pt x="264" y="-18" on="1"/>
+ <pt x="192" y="-12" on="0"/>
+ <pt x="72" y="34" on="0"/>
+ <pt x="29" y="74" on="1"/>
+ <pt x="81" y="146" on="1"/>
+ <pt x="123" y="110" on="0"/>
+ <pt x="207" y="73" on="0"/>
+ <pt x="264" y="69" on="1"/>
+ <pt x="264" y="301" on="1"/>
+ <pt x="249" y="304" on="1"/>
+ <pt x="148" y="323" on="0"/>
+ <pt x="43" y="420" on="0"/>
+ <pt x="43" y="502" on="1"/>
+ <pt x="43" y="559" on="0"/>
+ <pt x="99" y="650" on="0"/>
+ <pt x="199" y="707" on="0"/>
+ <pt x="264" y="715" on="1"/>
+ </contour>
+ <contour>
+ <pt x="137" y="502" on="1"/>
+ <pt x="137" y="470" on="0"/>
+ <pt x="160" y="428" on="0"/>
+ <pt x="214" y="402" on="0"/>
+ <pt x="261" y="392" on="1"/>
+ <pt x="264" y="627" on="1"/>
+ <pt x="203" y="618" on="0"/>
+ <pt x="137" y="553" on="0"/>
+ </contour>
+ <contour>
+ <pt x="354" y="69" on="1"/>
+ <pt x="423" y="76" on="0"/>
+ <pt x="486" y="135" on="0"/>
+ <pt x="486" y="183" on="1"/>
+ <pt x="486" y="211" on="0"/>
+ <pt x="462" y="250" on="0"/>
+ <pt x="405" y="275" on="0"/>
+ <pt x="354" y="285" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.BRACKET.500" xMin="29" yMin="-76" xMax="580" yMax="759">
+ <contour>
+ <pt x="264" y="759" on="1"/>
+ <pt x="354" y="759" on="1"/>
+ <pt x="354" y="715" on="1"/>
+ <pt x="415" y="709" on="0"/>
+ <pt x="519" y="662" on="0"/>
+ <pt x="562" y="620" on="1"/>
+ <pt x="509" y="548" on="1"/>
+ <pt x="464" y="592" on="0"/>
+ <pt x="370" y="630" on="0"/>
+ <pt x="308" y="630" on="1"/>
+ <pt x="226" y="630" on="0"/>
+ <pt x="137" y="562" on="0"/>
+ <pt x="137" y="502" on="1"/>
+ <pt x="137" y="470" on="0"/>
+ <pt x="160" y="428" on="0"/>
+ <pt x="214" y="402" on="0"/>
+ <pt x="261" y="392" on="1"/>
+ <pt x="360" y="372" on="1"/>
+ <pt x="469" y="350" on="0"/>
+ <pt x="580" y="263" on="0"/>
+ <pt x="580" y="184" on="1"/>
+ <pt x="580" y="102" on="0"/>
+ <pt x="459" y="-8" on="0"/>
+ <pt x="354" y="-18" on="1"/>
+ <pt x="354" y="-76" on="1"/>
+ <pt x="264" y="-76" on="1"/>
+ <pt x="264" y="-18" on="1"/>
+ <pt x="192" y="-12" on="0"/>
+ <pt x="72" y="34" on="0"/>
+ <pt x="29" y="74" on="1"/>
+ <pt x="81" y="146" on="1"/>
+ <pt x="115" y="118" on="0"/>
+ <pt x="180" y="83" on="0"/>
+ <pt x="259" y="67" on="0"/>
+ <pt x="310" y="67" on="1"/>
+ <pt x="403" y="67" on="0"/>
+ <pt x="486" y="128" on="0"/>
+ <pt x="486" y="183" on="1"/>
+ <pt x="486" y="212" on="0"/>
+ <pt x="461" y="251" on="0"/>
+ <pt x="401" y="277" on="0"/>
+ <pt x="348" y="286" on="1"/>
+ <pt x="249" y="304" on="1"/>
+ <pt x="148" y="323" on="0"/>
+ <pt x="43" y="420" on="0"/>
+ <pt x="43" y="502" on="1"/>
+ <pt x="43" y="559" on="0"/>
+ <pt x="99" y="650" on="0"/>
+ <pt x="199" y="707" on="0"/>
+ <pt x="264" y="715" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.bold" xMin="29" yMin="-76" xMax="580" yMax="759">
+ <contour>
+ <pt x="264" y="759" on="1"/>
+ <pt x="354" y="759" on="1"/>
+ <pt x="354" y="715" on="1"/>
+ <pt x="415" y="709" on="0"/>
+ <pt x="519" y="662" on="0"/>
+ <pt x="562" y="620" on="1"/>
+ <pt x="509" y="548" on="1"/>
+ <pt x="464" y="592" on="0"/>
+ <pt x="370" y="630" on="0"/>
+ <pt x="308" y="630" on="1"/>
+ <pt x="226" y="630" on="0"/>
+ <pt x="137" y="562" on="0"/>
+ <pt x="137" y="502" on="1"/>
+ <pt x="137" y="470" on="0"/>
+ <pt x="160" y="428" on="0"/>
+ <pt x="214" y="402" on="0"/>
+ <pt x="261" y="392" on="1"/>
+ <pt x="360" y="372" on="1"/>
+ <pt x="469" y="350" on="0"/>
+ <pt x="580" y="263" on="0"/>
+ <pt x="580" y="184" on="1"/>
+ <pt x="580" y="102" on="0"/>
+ <pt x="459" y="-8" on="0"/>
+ <pt x="354" y="-18" on="1"/>
+ <pt x="354" y="-76" on="1"/>
+ <pt x="264" y="-76" on="1"/>
+ <pt x="264" y="-18" on="1"/>
+ <pt x="192" y="-12" on="0"/>
+ <pt x="72" y="34" on="0"/>
+ <pt x="29" y="74" on="1"/>
+ <pt x="81" y="146" on="1"/>
+ <pt x="115" y="118" on="0"/>
+ <pt x="180" y="83" on="0"/>
+ <pt x="259" y="67" on="0"/>
+ <pt x="310" y="67" on="1"/>
+ <pt x="403" y="67" on="0"/>
+ <pt x="486" y="128" on="0"/>
+ <pt x="486" y="183" on="1"/>
+ <pt x="486" y="212" on="0"/>
+ <pt x="461" y="251" on="0"/>
+ <pt x="401" y="277" on="0"/>
+ <pt x="348" y="286" on="1"/>
+ <pt x="249" y="304" on="1"/>
+ <pt x="148" y="323" on="0"/>
+ <pt x="43" y="420" on="0"/>
+ <pt x="43" y="502" on="1"/>
+ <pt x="43" y="559" on="0"/>
+ <pt x="99" y="650" on="0"/>
+ <pt x="199" y="707" on="0"/>
+ <pt x="264" y="715" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;SimpleTwoAxis-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SimpleTwoAxis-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.bold"/>
+ <psName name="acutecomb"/>
+ <psName name="dollar.BRACKET.500"/>
+ </extraNames>
+ </post>
+
+ <GDEF>
+ <Version value="0x00010000"/>
+ <GlyphClassDef>
+ <ClassDef glyph="A" class="1"/>
+ <ClassDef glyph="Aacute" class="1"/>
+ <ClassDef glyph="acutecomb" class="3"/>
+ </GlyphClassDef>
+ </GDEF>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="0"/>
+ <FeatureIndex index="1" value="1"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=2 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="kern"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="mark"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ <Glyph value="V"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <!-- PairSetCount=3 -->
+ <PairSet index="0">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="1">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="2">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="O"/>
+ <Value1 XAdvance="-20"/>
+ </PairValueRecord>
+ </PairSet>
+ </PairPos>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="4"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MarkBasePos index="0" Format="1">
+ <MarkCoverage>
+ <Glyph value="acutecomb"/>
+ </MarkCoverage>
+ <BaseCoverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ </BaseCoverage>
+ <!-- ClassCount=1 -->
+ <MarkArray>
+ <!-- MarkCount=1 -->
+ <MarkRecord index="0">
+ <Class value="0"/>
+ <MarkAnchor Format="1">
+ <XCoordinate value="4"/>
+ <YCoordinate value="623"/>
+ </MarkAnchor>
+ </MarkRecord>
+ </MarkArray>
+ <BaseArray>
+ <!-- BaseCount=2 -->
+ <BaseRecord index="0">
+ <BaseAnchor index="0" Format="1">
+ <XCoordinate value="406"/>
+ <YCoordinate value="753"/>
+ </BaseAnchor>
+ </BaseRecord>
+ <BaseRecord index="1">
+ <BaseAnchor index="0" Format="1">
+ <XCoordinate value="406"/>
+ <YCoordinate value="753"/>
+ </BaseAnchor>
+ </BaseRecord>
+ </BaseArray>
+ </MarkBasePos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_incompatible_features/IncompatibleFeatures-Bold.ttx b/Tests/varLib/data/master_incompatible_features/IncompatibleFeatures-Bold.ttx
new file mode 100644
index 00000000..18aee9fa
--- /dev/null
+++ b/Tests/varLib/data/master_incompatible_features/IncompatibleFeatures-Bold.ttx
@@ -0,0 +1,578 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="Aacute"/>
+ <GlyphID id="3" name="O"/>
+ <GlyphID id="4" name="V"/>
+ <GlyphID id="5" name="space"/>
+ <GlyphID id="6" name="dollar"/>
+ <GlyphID id="7" name="dollar.bold"/>
+ <GlyphID id="8" name="acutecomb"/>
+ <GlyphID id="9" name="dollar.BRACKET.500"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x10cb3f3"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Jan 15 14:37:13 2021"/>
+ <modified value="Mon Mar 15 12:57:03 2021"/>
+ <xMin value="-141"/>
+ <yMin value="-200"/>
+ <xMax value="906"/>
+ <yMax value="949"/>
+ <macStyle value="00000000 00000001"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="911"/>
+ <minLeftSideBearing value="-141"/>
+ <minRightSideBearing value="-125"/>
+ <xMaxExtent value="906"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="10"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="10"/>
+ <maxPoints value="52"/>
+ <maxContours value="3"/>
+ <maxCompositePoints value="16"/>
+ <maxCompositeContours value="4"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="2"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="672"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 01000011"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 00100000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="769"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="2"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="911" lsb="5"/>
+ <mtx name="Aacute" width="911" lsb="5"/>
+ <mtx name="O" width="715" lsb="15"/>
+ <mtx name="V" width="911" lsb="5"/>
+ <mtx name="acutecomb" width="0" lsb="-141"/>
+ <mtx name="dollar" width="600" lsb="1"/>
+ <mtx name="dollar.BRACKET.500" width="600" lsb="1"/>
+ <mtx name="dollar.bold" width="600" lsb="1"/>
+ <mtx name="space" width="300" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="5" yMin="0" xMax="906" yMax="700">
+ <contour>
+ <pt x="705" y="0" on="1"/>
+ <pt x="906" y="0" on="1"/>
+ <pt x="556" y="700" on="1"/>
+ <pt x="355" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="5" y="0" on="1"/>
+ <pt x="206" y="0" on="1"/>
+ <pt x="556" y="700" on="1"/>
+ <pt x="355" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="640" y="311" on="1"/>
+ <pt x="190" y="311" on="1"/>
+ <pt x="190" y="191" on="1"/>
+ <pt x="640" y="191" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="Aacute" xMin="5" yMin="0" xMax="906" yMax="949">
+ <component glyphName="A" x="0" y="0" flags="0x204"/>
+ <component glyphName="acutecomb" x="479" y="124" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="O" xMin="15" yMin="-10" xMax="670" yMax="710">
+ <contour>
+ <pt x="342" y="-10" on="1"/>
+ <pt x="172" y="-10" on="0"/>
+ <pt x="15" y="163" on="0"/>
+ <pt x="15" y="350" on="1"/>
+ <pt x="15" y="538" on="0"/>
+ <pt x="172" y="710" on="0"/>
+ <pt x="342" y="710" on="1"/>
+ <pt x="513" y="710" on="0"/>
+ <pt x="670" y="538" on="0"/>
+ <pt x="670" y="350" on="1"/>
+ <pt x="670" y="163" on="0"/>
+ <pt x="513" y="-10" on="0"/>
+ </contour>
+ <contour>
+ <pt x="342" y="153" on="1"/>
+ <pt x="419" y="153" on="0"/>
+ <pt x="490" y="247" on="0"/>
+ <pt x="490" y="350" on="1"/>
+ <pt x="490" y="453" on="0"/>
+ <pt x="419" y="547" on="0"/>
+ <pt x="342" y="547" on="1"/>
+ <pt x="266" y="547" on="0"/>
+ <pt x="195" y="453" on="0"/>
+ <pt x="195" y="350" on="1"/>
+ <pt x="195" y="247" on="0"/>
+ <pt x="266" y="153" on="0"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="V" xMin="5" yMin="0" xMax="906" yMax="700">
+ <contour>
+ <pt x="355" y="0" on="1"/>
+ <pt x="705" y="700" on="1"/>
+ <pt x="906" y="700" on="1"/>
+ <pt x="556" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="355" y="0" on="1"/>
+ <pt x="5" y="700" on="1"/>
+ <pt x="206" y="700" on="1"/>
+ <pt x="556" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="acutecomb" xMin="-141" yMin="630" xMax="125" yMax="825">
+ <contour>
+ <pt x="-118" y="756" on="1"/>
+ <pt x="-141" y="630" on="1"/>
+ <pt x="102" y="699" on="1"/>
+ <pt x="125" y="825" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar" xMin="1" yMin="-98" xMax="595" yMax="789">
+ <contour>
+ <pt x="249" y="789" on="1"/>
+ <pt x="369" y="789" on="1"/>
+ <pt x="369" y="743" on="1"/>
+ <pt x="427" y="735" on="0"/>
+ <pt x="537" y="681" on="0"/>
+ <pt x="590" y="623" on="1"/>
+ <pt x="510" y="515" on="1"/>
+ <pt x="479" y="549" on="0"/>
+ <pt x="411" y="588" on="0"/>
+ <pt x="369" y="595" on="1"/>
+ <pt x="369" y="400" on="1"/>
+ <pt x="476" y="378" on="0"/>
+ <pt x="595" y="278" on="0"/>
+ <pt x="595" y="184" on="1"/>
+ <pt x="595" y="93" on="0"/>
+ <pt x="474" y="-32" on="0"/>
+ <pt x="369" y="-46" on="1"/>
+ <pt x="369" y="-98" on="1"/>
+ <pt x="249" y="-98" on="1"/>
+ <pt x="249" y="-47" on="1"/>
+ <pt x="176" y="-39" on="0"/>
+ <pt x="52" y="17" on="0"/>
+ <pt x="1" y="69" on="1"/>
+ <pt x="80" y="179" on="1"/>
+ <pt x="118" y="144" on="0"/>
+ <pt x="195" y="106" on="0"/>
+ <pt x="249" y="100" on="1"/>
+ <pt x="249" y="273" on="1"/>
+ <pt x="246" y="274" on="1"/>
+ <pt x="144" y="294" on="0"/>
+ <pt x="28" y="405" on="0"/>
+ <pt x="28" y="502" on="1"/>
+ <pt x="28" y="567" on="0"/>
+ <pt x="84" y="667" on="0"/>
+ <pt x="184" y="732" on="0"/>
+ <pt x="249" y="742" on="1"/>
+ </contour>
+ <contour>
+ <pt x="152" y="502" on="1"/>
+ <pt x="152" y="480" on="0"/>
+ <pt x="166" y="453" on="0"/>
+ <pt x="208" y="434" on="0"/>
+ <pt x="249" y="424" on="1"/>
+ <pt x="249" y="595" on="1"/>
+ <pt x="199" y="587" on="0"/>
+ <pt x="152" y="538" on="0"/>
+ </contour>
+ <contour>
+ <pt x="369" y="100" on="1"/>
+ <pt x="426" y="107" on="0"/>
+ <pt x="471" y="150" on="0"/>
+ <pt x="471" y="183" on="1"/>
+ <pt x="471" y="201" on="0"/>
+ <pt x="456" y="225" on="0"/>
+ <pt x="412" y="243" on="0"/>
+ <pt x="369" y="252" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.BRACKET.500" xMin="1" yMin="-98" xMax="595" yMax="789">
+ <contour>
+ <pt x="249" y="789" on="1"/>
+ <pt x="369" y="789" on="1"/>
+ <pt x="369" y="743" on="1"/>
+ <pt x="427" y="735" on="0"/>
+ <pt x="537" y="681" on="0"/>
+ <pt x="590" y="623" on="1"/>
+ <pt x="510" y="515" on="1"/>
+ <pt x="468" y="560" on="0"/>
+ <pt x="374" y="600" on="0"/>
+ <pt x="308" y="600" on="1"/>
+ <pt x="227" y="600" on="0"/>
+ <pt x="152" y="548" on="0"/>
+ <pt x="152" y="502" on="1"/>
+ <pt x="152" y="479" on="0"/>
+ <pt x="168" y="450" on="0"/>
+ <pt x="217" y="431" on="0"/>
+ <pt x="264" y="421" on="1"/>
+ <pt x="363" y="401" on="1"/>
+ <pt x="473" y="379" on="0"/>
+ <pt x="595" y="279" on="0"/>
+ <pt x="595" y="184" on="1"/>
+ <pt x="595" y="93" on="0"/>
+ <pt x="474" y="-32" on="0"/>
+ <pt x="369" y="-46" on="1"/>
+ <pt x="369" y="-98" on="1"/>
+ <pt x="249" y="-98" on="1"/>
+ <pt x="249" y="-47" on="1"/>
+ <pt x="176" y="-39" on="0"/>
+ <pt x="52" y="17" on="0"/>
+ <pt x="1" y="69" on="1"/>
+ <pt x="80" y="179" on="1"/>
+ <pt x="112" y="150" on="0"/>
+ <pt x="176" y="114" on="0"/>
+ <pt x="256" y="97" on="0"/>
+ <pt x="310" y="97" on="1"/>
+ <pt x="402" y="97" on="0"/>
+ <pt x="471" y="143" on="0"/>
+ <pt x="471" y="183" on="1"/>
+ <pt x="471" y="203" on="0"/>
+ <pt x="453" y="228" on="0"/>
+ <pt x="399" y="247" on="0"/>
+ <pt x="345" y="256" on="1"/>
+ <pt x="246" y="274" on="1"/>
+ <pt x="144" y="293" on="0"/>
+ <pt x="28" y="405" on="0"/>
+ <pt x="28" y="502" on="1"/>
+ <pt x="28" y="567" on="0"/>
+ <pt x="84" y="667" on="0"/>
+ <pt x="184" y="732" on="0"/>
+ <pt x="249" y="742" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.bold" xMin="1" yMin="-98" xMax="595" yMax="789">
+ <contour>
+ <pt x="249" y="789" on="1"/>
+ <pt x="369" y="789" on="1"/>
+ <pt x="369" y="743" on="1"/>
+ <pt x="427" y="735" on="0"/>
+ <pt x="537" y="681" on="0"/>
+ <pt x="590" y="623" on="1"/>
+ <pt x="510" y="515" on="1"/>
+ <pt x="468" y="560" on="0"/>
+ <pt x="374" y="600" on="0"/>
+ <pt x="308" y="600" on="1"/>
+ <pt x="227" y="600" on="0"/>
+ <pt x="152" y="548" on="0"/>
+ <pt x="152" y="502" on="1"/>
+ <pt x="152" y="479" on="0"/>
+ <pt x="168" y="450" on="0"/>
+ <pt x="217" y="431" on="0"/>
+ <pt x="264" y="421" on="1"/>
+ <pt x="363" y="401" on="1"/>
+ <pt x="473" y="379" on="0"/>
+ <pt x="595" y="279" on="0"/>
+ <pt x="595" y="184" on="1"/>
+ <pt x="595" y="93" on="0"/>
+ <pt x="474" y="-32" on="0"/>
+ <pt x="369" y="-46" on="1"/>
+ <pt x="369" y="-98" on="1"/>
+ <pt x="249" y="-98" on="1"/>
+ <pt x="249" y="-47" on="1"/>
+ <pt x="176" y="-39" on="0"/>
+ <pt x="52" y="17" on="0"/>
+ <pt x="1" y="69" on="1"/>
+ <pt x="80" y="179" on="1"/>
+ <pt x="112" y="150" on="0"/>
+ <pt x="176" y="114" on="0"/>
+ <pt x="256" y="97" on="0"/>
+ <pt x="310" y="97" on="1"/>
+ <pt x="402" y="97" on="0"/>
+ <pt x="471" y="143" on="0"/>
+ <pt x="471" y="183" on="1"/>
+ <pt x="471" y="203" on="0"/>
+ <pt x="453" y="228" on="0"/>
+ <pt x="399" y="247" on="0"/>
+ <pt x="345" y="256" on="1"/>
+ <pt x="246" y="274" on="1"/>
+ <pt x="144" y="293" on="0"/>
+ <pt x="28" y="405" on="0"/>
+ <pt x="28" y="502" on="1"/>
+ <pt x="28" y="567" on="0"/>
+ <pt x="84" y="667" on="0"/>
+ <pt x="184" y="732" on="0"/>
+ <pt x="249" y="742" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Bold
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;SimpleTwoAxis-Bold
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis Bold
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SimpleTwoAxis-Bold
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.bold"/>
+ <psName name="acutecomb"/>
+ <psName name="dollar.BRACKET.500"/>
+ </extraNames>
+ </post>
+
+ <GDEF>
+ <Version value="0x00010000"/>
+ <GlyphClassDef>
+ <ClassDef glyph="A" class="1"/>
+ <ClassDef glyph="Aacute" class="1"/>
+ <ClassDef glyph="acutecomb" class="3"/>
+ </GlyphClassDef>
+ </GDEF>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=2 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="kern"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ <Glyph value="V"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <!-- PairSetCount=3 -->
+ <PairSet index="0">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-120"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="1">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-120"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="2">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="O"/>
+ <Value1 XAdvance="-20"/>
+ </PairValueRecord>
+ </PairSet>
+ </PairPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_incompatible_features/IncompatibleFeatures-Regular.ttx b/Tests/varLib/data/master_incompatible_features/IncompatibleFeatures-Regular.ttx
new file mode 100644
index 00000000..cabb69a4
--- /dev/null
+++ b/Tests/varLib/data/master_incompatible_features/IncompatibleFeatures-Regular.ttx
@@ -0,0 +1,626 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="Aacute"/>
+ <GlyphID id="3" name="O"/>
+ <GlyphID id="4" name="V"/>
+ <GlyphID id="5" name="space"/>
+ <GlyphID id="6" name="dollar"/>
+ <GlyphID id="7" name="dollar.bold"/>
+ <GlyphID id="8" name="acutecomb"/>
+ <GlyphID id="9" name="dollar.BRACKET.500"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x3c7bc79b"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Jan 15 14:37:13 2021"/>
+ <modified value="Mon Mar 15 12:57:03 2021"/>
+ <xMin value="-141"/>
+ <yMin value="-200"/>
+ <xMax value="751"/>
+ <yMax value="915"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="756"/>
+ <minLeftSideBearing value="-141"/>
+ <minRightSideBearing value="-125"/>
+ <xMaxExtent value="751"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="10"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="10"/>
+ <maxPoints value="52"/>
+ <maxContours value="3"/>
+ <maxCompositePoints value="16"/>
+ <maxCompositeContours value="4"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="2"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="604"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 01000011"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="769"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="2"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="756" lsb="5"/>
+ <mtx name="Aacute" width="756" lsb="5"/>
+ <mtx name="O" width="664" lsb="30"/>
+ <mtx name="V" width="756" lsb="5"/>
+ <mtx name="acutecomb" width="0" lsb="-141"/>
+ <mtx name="dollar" width="600" lsb="29"/>
+ <mtx name="dollar.BRACKET.500" width="600" lsb="29"/>
+ <mtx name="dollar.bold" width="600" lsb="29"/>
+ <mtx name="space" width="200" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="5" yMin="0" xMax="751" yMax="700">
+ <contour>
+ <pt x="641" y="0" on="1"/>
+ <pt x="751" y="0" on="1"/>
+ <pt x="433" y="700" on="1"/>
+ <pt x="323" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="5" y="0" on="1"/>
+ <pt x="115" y="0" on="1"/>
+ <pt x="433" y="700" on="1"/>
+ <pt x="323" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="567" y="284" on="1"/>
+ <pt x="152" y="284" on="1"/>
+ <pt x="152" y="204" on="1"/>
+ <pt x="567" y="204" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="Aacute" xMin="5" yMin="0" xMax="751" yMax="915">
+ <component glyphName="A" x="0" y="0" flags="0x204"/>
+ <component glyphName="acutecomb" x="402" y="130" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="O" xMin="30" yMin="-10" xMax="634" yMax="710">
+ <contour>
+ <pt x="332" y="-10" on="1"/>
+ <pt x="181" y="-10" on="0"/>
+ <pt x="30" y="169" on="0"/>
+ <pt x="30" y="350" on="1"/>
+ <pt x="30" y="531" on="0"/>
+ <pt x="181" y="710" on="0"/>
+ <pt x="332" y="710" on="1"/>
+ <pt x="484" y="710" on="0"/>
+ <pt x="634" y="531" on="0"/>
+ <pt x="634" y="350" on="1"/>
+ <pt x="634" y="169" on="0"/>
+ <pt x="484" y="-10" on="0"/>
+ </contour>
+ <contour>
+ <pt x="332" y="74" on="1"/>
+ <pt x="438" y="74" on="0"/>
+ <pt x="544" y="212" on="0"/>
+ <pt x="544" y="350" on="1"/>
+ <pt x="544" y="488" on="0"/>
+ <pt x="438" y="626" on="0"/>
+ <pt x="332" y="626" on="1"/>
+ <pt x="226" y="626" on="0"/>
+ <pt x="120" y="488" on="0"/>
+ <pt x="120" y="350" on="1"/>
+ <pt x="120" y="212" on="0"/>
+ <pt x="226" y="74" on="0"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="V" xMin="5" yMin="0" xMax="751" yMax="700">
+ <contour>
+ <pt x="323" y="0" on="1"/>
+ <pt x="641" y="700" on="1"/>
+ <pt x="751" y="700" on="1"/>
+ <pt x="433" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="323" y="0" on="1"/>
+ <pt x="5" y="700" on="1"/>
+ <pt x="115" y="700" on="1"/>
+ <pt x="433" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="acutecomb" xMin="-141" yMin="630" xMax="125" yMax="785">
+ <contour>
+ <pt x="-118" y="716" on="1"/>
+ <pt x="-141" y="630" on="1"/>
+ <pt x="102" y="699" on="1"/>
+ <pt x="125" y="785" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar" xMin="29" yMin="-68" xMax="580" yMax="759">
+ <contour>
+ <pt x="264" y="759" on="1"/>
+ <pt x="354" y="759" on="1"/>
+ <pt x="354" y="715" on="1"/>
+ <pt x="415" y="709" on="0"/>
+ <pt x="519" y="662" on="0"/>
+ <pt x="562" y="620" on="1"/>
+ <pt x="509" y="548" on="1"/>
+ <pt x="473" y="584" on="0"/>
+ <pt x="398" y="621" on="0"/>
+ <pt x="354" y="627" on="1"/>
+ <pt x="354" y="373" on="1"/>
+ <pt x="467" y="351" on="0"/>
+ <pt x="580" y="263" on="0"/>
+ <pt x="580" y="184" on="1"/>
+ <pt x="580" y="102" on="0"/>
+ <pt x="459" y="-8" on="0"/>
+ <pt x="354" y="-18" on="1"/>
+ <pt x="354" y="-68" on="1"/>
+ <pt x="264" y="-68" on="1"/>
+ <pt x="264" y="-18" on="1"/>
+ <pt x="192" y="-12" on="0"/>
+ <pt x="72" y="34" on="0"/>
+ <pt x="29" y="74" on="1"/>
+ <pt x="81" y="146" on="1"/>
+ <pt x="123" y="110" on="0"/>
+ <pt x="207" y="73" on="0"/>
+ <pt x="264" y="69" on="1"/>
+ <pt x="264" y="301" on="1"/>
+ <pt x="249" y="304" on="1"/>
+ <pt x="148" y="323" on="0"/>
+ <pt x="43" y="420" on="0"/>
+ <pt x="43" y="502" on="1"/>
+ <pt x="43" y="559" on="0"/>
+ <pt x="99" y="650" on="0"/>
+ <pt x="199" y="707" on="0"/>
+ <pt x="264" y="715" on="1"/>
+ </contour>
+ <contour>
+ <pt x="137" y="502" on="1"/>
+ <pt x="137" y="470" on="0"/>
+ <pt x="160" y="428" on="0"/>
+ <pt x="214" y="402" on="0"/>
+ <pt x="261" y="392" on="1"/>
+ <pt x="264" y="627" on="1"/>
+ <pt x="203" y="618" on="0"/>
+ <pt x="137" y="553" on="0"/>
+ </contour>
+ <contour>
+ <pt x="354" y="69" on="1"/>
+ <pt x="423" y="76" on="0"/>
+ <pt x="486" y="135" on="0"/>
+ <pt x="486" y="183" on="1"/>
+ <pt x="486" y="211" on="0"/>
+ <pt x="462" y="250" on="0"/>
+ <pt x="405" y="275" on="0"/>
+ <pt x="354" y="285" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.BRACKET.500" xMin="29" yMin="-76" xMax="580" yMax="759">
+ <contour>
+ <pt x="264" y="759" on="1"/>
+ <pt x="354" y="759" on="1"/>
+ <pt x="354" y="715" on="1"/>
+ <pt x="415" y="709" on="0"/>
+ <pt x="519" y="662" on="0"/>
+ <pt x="562" y="620" on="1"/>
+ <pt x="509" y="548" on="1"/>
+ <pt x="464" y="592" on="0"/>
+ <pt x="370" y="630" on="0"/>
+ <pt x="308" y="630" on="1"/>
+ <pt x="226" y="630" on="0"/>
+ <pt x="137" y="562" on="0"/>
+ <pt x="137" y="502" on="1"/>
+ <pt x="137" y="470" on="0"/>
+ <pt x="160" y="428" on="0"/>
+ <pt x="214" y="402" on="0"/>
+ <pt x="261" y="392" on="1"/>
+ <pt x="360" y="372" on="1"/>
+ <pt x="469" y="350" on="0"/>
+ <pt x="580" y="263" on="0"/>
+ <pt x="580" y="184" on="1"/>
+ <pt x="580" y="102" on="0"/>
+ <pt x="459" y="-8" on="0"/>
+ <pt x="354" y="-18" on="1"/>
+ <pt x="354" y="-76" on="1"/>
+ <pt x="264" y="-76" on="1"/>
+ <pt x="264" y="-18" on="1"/>
+ <pt x="192" y="-12" on="0"/>
+ <pt x="72" y="34" on="0"/>
+ <pt x="29" y="74" on="1"/>
+ <pt x="81" y="146" on="1"/>
+ <pt x="115" y="118" on="0"/>
+ <pt x="180" y="83" on="0"/>
+ <pt x="259" y="67" on="0"/>
+ <pt x="310" y="67" on="1"/>
+ <pt x="403" y="67" on="0"/>
+ <pt x="486" y="128" on="0"/>
+ <pt x="486" y="183" on="1"/>
+ <pt x="486" y="212" on="0"/>
+ <pt x="461" y="251" on="0"/>
+ <pt x="401" y="277" on="0"/>
+ <pt x="348" y="286" on="1"/>
+ <pt x="249" y="304" on="1"/>
+ <pt x="148" y="323" on="0"/>
+ <pt x="43" y="420" on="0"/>
+ <pt x="43" y="502" on="1"/>
+ <pt x="43" y="559" on="0"/>
+ <pt x="99" y="650" on="0"/>
+ <pt x="199" y="707" on="0"/>
+ <pt x="264" y="715" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.bold" xMin="29" yMin="-76" xMax="580" yMax="759">
+ <contour>
+ <pt x="264" y="759" on="1"/>
+ <pt x="354" y="759" on="1"/>
+ <pt x="354" y="715" on="1"/>
+ <pt x="415" y="709" on="0"/>
+ <pt x="519" y="662" on="0"/>
+ <pt x="562" y="620" on="1"/>
+ <pt x="509" y="548" on="1"/>
+ <pt x="464" y="592" on="0"/>
+ <pt x="370" y="630" on="0"/>
+ <pt x="308" y="630" on="1"/>
+ <pt x="226" y="630" on="0"/>
+ <pt x="137" y="562" on="0"/>
+ <pt x="137" y="502" on="1"/>
+ <pt x="137" y="470" on="0"/>
+ <pt x="160" y="428" on="0"/>
+ <pt x="214" y="402" on="0"/>
+ <pt x="261" y="392" on="1"/>
+ <pt x="360" y="372" on="1"/>
+ <pt x="469" y="350" on="0"/>
+ <pt x="580" y="263" on="0"/>
+ <pt x="580" y="184" on="1"/>
+ <pt x="580" y="102" on="0"/>
+ <pt x="459" y="-8" on="0"/>
+ <pt x="354" y="-18" on="1"/>
+ <pt x="354" y="-76" on="1"/>
+ <pt x="264" y="-76" on="1"/>
+ <pt x="264" y="-18" on="1"/>
+ <pt x="192" y="-12" on="0"/>
+ <pt x="72" y="34" on="0"/>
+ <pt x="29" y="74" on="1"/>
+ <pt x="81" y="146" on="1"/>
+ <pt x="115" y="118" on="0"/>
+ <pt x="180" y="83" on="0"/>
+ <pt x="259" y="67" on="0"/>
+ <pt x="310" y="67" on="1"/>
+ <pt x="403" y="67" on="0"/>
+ <pt x="486" y="128" on="0"/>
+ <pt x="486" y="183" on="1"/>
+ <pt x="486" y="212" on="0"/>
+ <pt x="461" y="251" on="0"/>
+ <pt x="401" y="277" on="0"/>
+ <pt x="348" y="286" on="1"/>
+ <pt x="249" y="304" on="1"/>
+ <pt x="148" y="323" on="0"/>
+ <pt x="43" y="420" on="0"/>
+ <pt x="43" y="502" on="1"/>
+ <pt x="43" y="559" on="0"/>
+ <pt x="99" y="650" on="0"/>
+ <pt x="199" y="707" on="0"/>
+ <pt x="264" y="715" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;SimpleTwoAxis-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SimpleTwoAxis-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.bold"/>
+ <psName name="acutecomb"/>
+ <psName name="dollar.BRACKET.500"/>
+ </extraNames>
+ </post>
+
+ <GDEF>
+ <Version value="0x00010000"/>
+ <GlyphClassDef>
+ <ClassDef glyph="A" class="1"/>
+ <ClassDef glyph="Aacute" class="1"/>
+ <ClassDef glyph="acutecomb" class="3"/>
+ </GlyphClassDef>
+ </GDEF>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="0"/>
+ <FeatureIndex index="1" value="1"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=2 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="kern"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="mark"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ <Glyph value="V"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <!-- PairSetCount=3 -->
+ <PairSet index="0">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="1">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="2">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="O"/>
+ <Value1 XAdvance="-20"/>
+ </PairValueRecord>
+ </PairSet>
+ </PairPos>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="4"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MarkBasePos index="0" Format="1">
+ <MarkCoverage>
+ <Glyph value="acutecomb"/>
+ </MarkCoverage>
+ <BaseCoverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ </BaseCoverage>
+ <!-- ClassCount=1 -->
+ <MarkArray>
+ <!-- MarkCount=1 -->
+ <MarkRecord index="0">
+ <Class value="0"/>
+ <MarkAnchor Format="1">
+ <XCoordinate value="4"/>
+ <YCoordinate value="623"/>
+ </MarkAnchor>
+ </MarkRecord>
+ </MarkArray>
+ <BaseArray>
+ <!-- BaseCount=2 -->
+ <BaseRecord index="0">
+ <BaseAnchor index="0" Format="1">
+ <XCoordinate value="406"/>
+ <YCoordinate value="753"/>
+ </BaseAnchor>
+ </BaseRecord>
+ <BaseRecord index="1">
+ <BaseAnchor index="0" Format="1">
+ <XCoordinate value="406"/>
+ <YCoordinate value="753"/>
+ </BaseAnchor>
+ </BaseRecord>
+ </BaseArray>
+ </MarkBasePos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_incompatible_lookup_types/IncompatibleLookupTypes-Bold.ttx b/Tests/varLib/data/master_incompatible_lookup_types/IncompatibleLookupTypes-Bold.ttx
new file mode 100644
index 00000000..6a282237
--- /dev/null
+++ b/Tests/varLib/data/master_incompatible_lookup_types/IncompatibleLookupTypes-Bold.ttx
@@ -0,0 +1,622 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="Aacute"/>
+ <GlyphID id="3" name="O"/>
+ <GlyphID id="4" name="V"/>
+ <GlyphID id="5" name="space"/>
+ <GlyphID id="6" name="dollar"/>
+ <GlyphID id="7" name="dollar.bold"/>
+ <GlyphID id="8" name="acutecomb"/>
+ <GlyphID id="9" name="dollar.BRACKET.500"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x10cb3f3"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Jan 15 14:37:13 2021"/>
+ <modified value="Mon Mar 15 12:57:03 2021"/>
+ <xMin value="-141"/>
+ <yMin value="-200"/>
+ <xMax value="906"/>
+ <yMax value="949"/>
+ <macStyle value="00000000 00000001"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="911"/>
+ <minLeftSideBearing value="-141"/>
+ <minRightSideBearing value="-125"/>
+ <xMaxExtent value="906"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="10"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="10"/>
+ <maxPoints value="52"/>
+ <maxContours value="3"/>
+ <maxCompositePoints value="16"/>
+ <maxCompositeContours value="4"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="2"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="672"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 01000011"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 00100000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="769"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="2"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="911" lsb="5"/>
+ <mtx name="Aacute" width="911" lsb="5"/>
+ <mtx name="O" width="715" lsb="15"/>
+ <mtx name="V" width="911" lsb="5"/>
+ <mtx name="acutecomb" width="0" lsb="-141"/>
+ <mtx name="dollar" width="600" lsb="1"/>
+ <mtx name="dollar.BRACKET.500" width="600" lsb="1"/>
+ <mtx name="dollar.bold" width="600" lsb="1"/>
+ <mtx name="space" width="300" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="5" yMin="0" xMax="906" yMax="700">
+ <contour>
+ <pt x="705" y="0" on="1"/>
+ <pt x="906" y="0" on="1"/>
+ <pt x="556" y="700" on="1"/>
+ <pt x="355" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="5" y="0" on="1"/>
+ <pt x="206" y="0" on="1"/>
+ <pt x="556" y="700" on="1"/>
+ <pt x="355" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="640" y="311" on="1"/>
+ <pt x="190" y="311" on="1"/>
+ <pt x="190" y="191" on="1"/>
+ <pt x="640" y="191" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="Aacute" xMin="5" yMin="0" xMax="906" yMax="949">
+ <component glyphName="A" x="0" y="0" flags="0x204"/>
+ <component glyphName="acutecomb" x="479" y="124" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="O" xMin="15" yMin="-10" xMax="670" yMax="710">
+ <contour>
+ <pt x="342" y="-10" on="1"/>
+ <pt x="172" y="-10" on="0"/>
+ <pt x="15" y="163" on="0"/>
+ <pt x="15" y="350" on="1"/>
+ <pt x="15" y="538" on="0"/>
+ <pt x="172" y="710" on="0"/>
+ <pt x="342" y="710" on="1"/>
+ <pt x="513" y="710" on="0"/>
+ <pt x="670" y="538" on="0"/>
+ <pt x="670" y="350" on="1"/>
+ <pt x="670" y="163" on="0"/>
+ <pt x="513" y="-10" on="0"/>
+ </contour>
+ <contour>
+ <pt x="342" y="153" on="1"/>
+ <pt x="419" y="153" on="0"/>
+ <pt x="490" y="247" on="0"/>
+ <pt x="490" y="350" on="1"/>
+ <pt x="490" y="453" on="0"/>
+ <pt x="419" y="547" on="0"/>
+ <pt x="342" y="547" on="1"/>
+ <pt x="266" y="547" on="0"/>
+ <pt x="195" y="453" on="0"/>
+ <pt x="195" y="350" on="1"/>
+ <pt x="195" y="247" on="0"/>
+ <pt x="266" y="153" on="0"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="V" xMin="5" yMin="0" xMax="906" yMax="700">
+ <contour>
+ <pt x="355" y="0" on="1"/>
+ <pt x="705" y="700" on="1"/>
+ <pt x="906" y="700" on="1"/>
+ <pt x="556" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="355" y="0" on="1"/>
+ <pt x="5" y="700" on="1"/>
+ <pt x="206" y="700" on="1"/>
+ <pt x="556" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="acutecomb" xMin="-141" yMin="630" xMax="125" yMax="825">
+ <contour>
+ <pt x="-118" y="756" on="1"/>
+ <pt x="-141" y="630" on="1"/>
+ <pt x="102" y="699" on="1"/>
+ <pt x="125" y="825" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar" xMin="1" yMin="-98" xMax="595" yMax="789">
+ <contour>
+ <pt x="249" y="789" on="1"/>
+ <pt x="369" y="789" on="1"/>
+ <pt x="369" y="743" on="1"/>
+ <pt x="427" y="735" on="0"/>
+ <pt x="537" y="681" on="0"/>
+ <pt x="590" y="623" on="1"/>
+ <pt x="510" y="515" on="1"/>
+ <pt x="479" y="549" on="0"/>
+ <pt x="411" y="588" on="0"/>
+ <pt x="369" y="595" on="1"/>
+ <pt x="369" y="400" on="1"/>
+ <pt x="476" y="378" on="0"/>
+ <pt x="595" y="278" on="0"/>
+ <pt x="595" y="184" on="1"/>
+ <pt x="595" y="93" on="0"/>
+ <pt x="474" y="-32" on="0"/>
+ <pt x="369" y="-46" on="1"/>
+ <pt x="369" y="-98" on="1"/>
+ <pt x="249" y="-98" on="1"/>
+ <pt x="249" y="-47" on="1"/>
+ <pt x="176" y="-39" on="0"/>
+ <pt x="52" y="17" on="0"/>
+ <pt x="1" y="69" on="1"/>
+ <pt x="80" y="179" on="1"/>
+ <pt x="118" y="144" on="0"/>
+ <pt x="195" y="106" on="0"/>
+ <pt x="249" y="100" on="1"/>
+ <pt x="249" y="273" on="1"/>
+ <pt x="246" y="274" on="1"/>
+ <pt x="144" y="294" on="0"/>
+ <pt x="28" y="405" on="0"/>
+ <pt x="28" y="502" on="1"/>
+ <pt x="28" y="567" on="0"/>
+ <pt x="84" y="667" on="0"/>
+ <pt x="184" y="732" on="0"/>
+ <pt x="249" y="742" on="1"/>
+ </contour>
+ <contour>
+ <pt x="152" y="502" on="1"/>
+ <pt x="152" y="480" on="0"/>
+ <pt x="166" y="453" on="0"/>
+ <pt x="208" y="434" on="0"/>
+ <pt x="249" y="424" on="1"/>
+ <pt x="249" y="595" on="1"/>
+ <pt x="199" y="587" on="0"/>
+ <pt x="152" y="538" on="0"/>
+ </contour>
+ <contour>
+ <pt x="369" y="100" on="1"/>
+ <pt x="426" y="107" on="0"/>
+ <pt x="471" y="150" on="0"/>
+ <pt x="471" y="183" on="1"/>
+ <pt x="471" y="201" on="0"/>
+ <pt x="456" y="225" on="0"/>
+ <pt x="412" y="243" on="0"/>
+ <pt x="369" y="252" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.BRACKET.500" xMin="1" yMin="-98" xMax="595" yMax="789">
+ <contour>
+ <pt x="249" y="789" on="1"/>
+ <pt x="369" y="789" on="1"/>
+ <pt x="369" y="743" on="1"/>
+ <pt x="427" y="735" on="0"/>
+ <pt x="537" y="681" on="0"/>
+ <pt x="590" y="623" on="1"/>
+ <pt x="510" y="515" on="1"/>
+ <pt x="468" y="560" on="0"/>
+ <pt x="374" y="600" on="0"/>
+ <pt x="308" y="600" on="1"/>
+ <pt x="227" y="600" on="0"/>
+ <pt x="152" y="548" on="0"/>
+ <pt x="152" y="502" on="1"/>
+ <pt x="152" y="479" on="0"/>
+ <pt x="168" y="450" on="0"/>
+ <pt x="217" y="431" on="0"/>
+ <pt x="264" y="421" on="1"/>
+ <pt x="363" y="401" on="1"/>
+ <pt x="473" y="379" on="0"/>
+ <pt x="595" y="279" on="0"/>
+ <pt x="595" y="184" on="1"/>
+ <pt x="595" y="93" on="0"/>
+ <pt x="474" y="-32" on="0"/>
+ <pt x="369" y="-46" on="1"/>
+ <pt x="369" y="-98" on="1"/>
+ <pt x="249" y="-98" on="1"/>
+ <pt x="249" y="-47" on="1"/>
+ <pt x="176" y="-39" on="0"/>
+ <pt x="52" y="17" on="0"/>
+ <pt x="1" y="69" on="1"/>
+ <pt x="80" y="179" on="1"/>
+ <pt x="112" y="150" on="0"/>
+ <pt x="176" y="114" on="0"/>
+ <pt x="256" y="97" on="0"/>
+ <pt x="310" y="97" on="1"/>
+ <pt x="402" y="97" on="0"/>
+ <pt x="471" y="143" on="0"/>
+ <pt x="471" y="183" on="1"/>
+ <pt x="471" y="203" on="0"/>
+ <pt x="453" y="228" on="0"/>
+ <pt x="399" y="247" on="0"/>
+ <pt x="345" y="256" on="1"/>
+ <pt x="246" y="274" on="1"/>
+ <pt x="144" y="293" on="0"/>
+ <pt x="28" y="405" on="0"/>
+ <pt x="28" y="502" on="1"/>
+ <pt x="28" y="567" on="0"/>
+ <pt x="84" y="667" on="0"/>
+ <pt x="184" y="732" on="0"/>
+ <pt x="249" y="742" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.bold" xMin="1" yMin="-98" xMax="595" yMax="789">
+ <contour>
+ <pt x="249" y="789" on="1"/>
+ <pt x="369" y="789" on="1"/>
+ <pt x="369" y="743" on="1"/>
+ <pt x="427" y="735" on="0"/>
+ <pt x="537" y="681" on="0"/>
+ <pt x="590" y="623" on="1"/>
+ <pt x="510" y="515" on="1"/>
+ <pt x="468" y="560" on="0"/>
+ <pt x="374" y="600" on="0"/>
+ <pt x="308" y="600" on="1"/>
+ <pt x="227" y="600" on="0"/>
+ <pt x="152" y="548" on="0"/>
+ <pt x="152" y="502" on="1"/>
+ <pt x="152" y="479" on="0"/>
+ <pt x="168" y="450" on="0"/>
+ <pt x="217" y="431" on="0"/>
+ <pt x="264" y="421" on="1"/>
+ <pt x="363" y="401" on="1"/>
+ <pt x="473" y="379" on="0"/>
+ <pt x="595" y="279" on="0"/>
+ <pt x="595" y="184" on="1"/>
+ <pt x="595" y="93" on="0"/>
+ <pt x="474" y="-32" on="0"/>
+ <pt x="369" y="-46" on="1"/>
+ <pt x="369" y="-98" on="1"/>
+ <pt x="249" y="-98" on="1"/>
+ <pt x="249" y="-47" on="1"/>
+ <pt x="176" y="-39" on="0"/>
+ <pt x="52" y="17" on="0"/>
+ <pt x="1" y="69" on="1"/>
+ <pt x="80" y="179" on="1"/>
+ <pt x="112" y="150" on="0"/>
+ <pt x="176" y="114" on="0"/>
+ <pt x="256" y="97" on="0"/>
+ <pt x="310" y="97" on="1"/>
+ <pt x="402" y="97" on="0"/>
+ <pt x="471" y="143" on="0"/>
+ <pt x="471" y="183" on="1"/>
+ <pt x="471" y="203" on="0"/>
+ <pt x="453" y="228" on="0"/>
+ <pt x="399" y="247" on="0"/>
+ <pt x="345" y="256" on="1"/>
+ <pt x="246" y="274" on="1"/>
+ <pt x="144" y="293" on="0"/>
+ <pt x="28" y="405" on="0"/>
+ <pt x="28" y="502" on="1"/>
+ <pt x="28" y="567" on="0"/>
+ <pt x="84" y="667" on="0"/>
+ <pt x="184" y="732" on="0"/>
+ <pt x="249" y="742" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Bold
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;SimpleTwoAxis-Bold
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis Bold
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SimpleTwoAxis-Bold
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.bold"/>
+ <psName name="acutecomb"/>
+ <psName name="dollar.BRACKET.500"/>
+ </extraNames>
+ </post>
+
+ <GDEF>
+ <Version value="0x00010000"/>
+ <GlyphClassDef>
+ <ClassDef glyph="A" class="1"/>
+ <ClassDef glyph="Aacute" class="1"/>
+ <ClassDef glyph="acutecomb" class="3"/>
+ </GlyphClassDef>
+ </GDEF>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="0"/>
+ <FeatureIndex index="1" value="1"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=2 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="kern"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="mark"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ <Glyph value="V"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <!-- PairSetCount=3 -->
+ <PairSet index="0">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="1">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="2">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="O"/>
+ <Value1 XAdvance="-20"/>
+ </PairValueRecord>
+ </PairSet>
+ </PairPos>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="4"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ <Glyph value="V"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <!-- PairSetCount=3 -->
+ <PairSet index="0">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="1">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="2">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="O"/>
+ <Value1 XAdvance="-20"/>
+ </PairValueRecord>
+ </PairSet>
+ </PairPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_incompatible_lookup_types/IncompatibleLookupTypes-Regular.ttx b/Tests/varLib/data/master_incompatible_lookup_types/IncompatibleLookupTypes-Regular.ttx
new file mode 100644
index 00000000..dc6eb17c
--- /dev/null
+++ b/Tests/varLib/data/master_incompatible_lookup_types/IncompatibleLookupTypes-Regular.ttx
@@ -0,0 +1,626 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="Aacute"/>
+ <GlyphID id="3" name="O"/>
+ <GlyphID id="4" name="V"/>
+ <GlyphID id="5" name="space"/>
+ <GlyphID id="6" name="dollar"/>
+ <GlyphID id="7" name="dollar.bold"/>
+ <GlyphID id="8" name="acutecomb"/>
+ <GlyphID id="9" name="dollar.BRACKET.500"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x3c7bc79b"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Jan 15 14:37:13 2021"/>
+ <modified value="Mon Mar 15 12:57:03 2021"/>
+ <xMin value="-141"/>
+ <yMin value="-200"/>
+ <xMax value="751"/>
+ <yMax value="915"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="756"/>
+ <minLeftSideBearing value="-141"/>
+ <minRightSideBearing value="-125"/>
+ <xMaxExtent value="751"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="10"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="10"/>
+ <maxPoints value="52"/>
+ <maxContours value="3"/>
+ <maxCompositePoints value="16"/>
+ <maxCompositeContours value="4"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="2"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="604"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 01000011"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="769"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="2"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="756" lsb="5"/>
+ <mtx name="Aacute" width="756" lsb="5"/>
+ <mtx name="O" width="664" lsb="30"/>
+ <mtx name="V" width="756" lsb="5"/>
+ <mtx name="acutecomb" width="0" lsb="-141"/>
+ <mtx name="dollar" width="600" lsb="29"/>
+ <mtx name="dollar.BRACKET.500" width="600" lsb="29"/>
+ <mtx name="dollar.bold" width="600" lsb="29"/>
+ <mtx name="space" width="200" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O -->
+ <map code="0x56" name="V"/><!-- LATIN CAPITAL LETTER V -->
+ <map code="0xc1" name="Aacute"/><!-- LATIN CAPITAL LETTER A WITH ACUTE -->
+ <map code="0x301" name="acutecomb"/><!-- COMBINING ACUTE ACCENT -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="5" yMin="0" xMax="751" yMax="700">
+ <contour>
+ <pt x="641" y="0" on="1"/>
+ <pt x="751" y="0" on="1"/>
+ <pt x="433" y="700" on="1"/>
+ <pt x="323" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="5" y="0" on="1"/>
+ <pt x="115" y="0" on="1"/>
+ <pt x="433" y="700" on="1"/>
+ <pt x="323" y="700" on="1"/>
+ </contour>
+ <contour>
+ <pt x="567" y="284" on="1"/>
+ <pt x="152" y="284" on="1"/>
+ <pt x="152" y="204" on="1"/>
+ <pt x="567" y="204" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="Aacute" xMin="5" yMin="0" xMax="751" yMax="915">
+ <component glyphName="A" x="0" y="0" flags="0x204"/>
+ <component glyphName="acutecomb" x="402" y="130" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="O" xMin="30" yMin="-10" xMax="634" yMax="710">
+ <contour>
+ <pt x="332" y="-10" on="1"/>
+ <pt x="181" y="-10" on="0"/>
+ <pt x="30" y="169" on="0"/>
+ <pt x="30" y="350" on="1"/>
+ <pt x="30" y="531" on="0"/>
+ <pt x="181" y="710" on="0"/>
+ <pt x="332" y="710" on="1"/>
+ <pt x="484" y="710" on="0"/>
+ <pt x="634" y="531" on="0"/>
+ <pt x="634" y="350" on="1"/>
+ <pt x="634" y="169" on="0"/>
+ <pt x="484" y="-10" on="0"/>
+ </contour>
+ <contour>
+ <pt x="332" y="74" on="1"/>
+ <pt x="438" y="74" on="0"/>
+ <pt x="544" y="212" on="0"/>
+ <pt x="544" y="350" on="1"/>
+ <pt x="544" y="488" on="0"/>
+ <pt x="438" y="626" on="0"/>
+ <pt x="332" y="626" on="1"/>
+ <pt x="226" y="626" on="0"/>
+ <pt x="120" y="488" on="0"/>
+ <pt x="120" y="350" on="1"/>
+ <pt x="120" y="212" on="0"/>
+ <pt x="226" y="74" on="0"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="V" xMin="5" yMin="0" xMax="751" yMax="700">
+ <contour>
+ <pt x="323" y="0" on="1"/>
+ <pt x="641" y="700" on="1"/>
+ <pt x="751" y="700" on="1"/>
+ <pt x="433" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="323" y="0" on="1"/>
+ <pt x="5" y="700" on="1"/>
+ <pt x="115" y="700" on="1"/>
+ <pt x="433" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="acutecomb" xMin="-141" yMin="630" xMax="125" yMax="785">
+ <contour>
+ <pt x="-118" y="716" on="1"/>
+ <pt x="-141" y="630" on="1"/>
+ <pt x="102" y="699" on="1"/>
+ <pt x="125" y="785" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar" xMin="29" yMin="-68" xMax="580" yMax="759">
+ <contour>
+ <pt x="264" y="759" on="1"/>
+ <pt x="354" y="759" on="1"/>
+ <pt x="354" y="715" on="1"/>
+ <pt x="415" y="709" on="0"/>
+ <pt x="519" y="662" on="0"/>
+ <pt x="562" y="620" on="1"/>
+ <pt x="509" y="548" on="1"/>
+ <pt x="473" y="584" on="0"/>
+ <pt x="398" y="621" on="0"/>
+ <pt x="354" y="627" on="1"/>
+ <pt x="354" y="373" on="1"/>
+ <pt x="467" y="351" on="0"/>
+ <pt x="580" y="263" on="0"/>
+ <pt x="580" y="184" on="1"/>
+ <pt x="580" y="102" on="0"/>
+ <pt x="459" y="-8" on="0"/>
+ <pt x="354" y="-18" on="1"/>
+ <pt x="354" y="-68" on="1"/>
+ <pt x="264" y="-68" on="1"/>
+ <pt x="264" y="-18" on="1"/>
+ <pt x="192" y="-12" on="0"/>
+ <pt x="72" y="34" on="0"/>
+ <pt x="29" y="74" on="1"/>
+ <pt x="81" y="146" on="1"/>
+ <pt x="123" y="110" on="0"/>
+ <pt x="207" y="73" on="0"/>
+ <pt x="264" y="69" on="1"/>
+ <pt x="264" y="301" on="1"/>
+ <pt x="249" y="304" on="1"/>
+ <pt x="148" y="323" on="0"/>
+ <pt x="43" y="420" on="0"/>
+ <pt x="43" y="502" on="1"/>
+ <pt x="43" y="559" on="0"/>
+ <pt x="99" y="650" on="0"/>
+ <pt x="199" y="707" on="0"/>
+ <pt x="264" y="715" on="1"/>
+ </contour>
+ <contour>
+ <pt x="137" y="502" on="1"/>
+ <pt x="137" y="470" on="0"/>
+ <pt x="160" y="428" on="0"/>
+ <pt x="214" y="402" on="0"/>
+ <pt x="261" y="392" on="1"/>
+ <pt x="264" y="627" on="1"/>
+ <pt x="203" y="618" on="0"/>
+ <pt x="137" y="553" on="0"/>
+ </contour>
+ <contour>
+ <pt x="354" y="69" on="1"/>
+ <pt x="423" y="76" on="0"/>
+ <pt x="486" y="135" on="0"/>
+ <pt x="486" y="183" on="1"/>
+ <pt x="486" y="211" on="0"/>
+ <pt x="462" y="250" on="0"/>
+ <pt x="405" y="275" on="0"/>
+ <pt x="354" y="285" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.BRACKET.500" xMin="29" yMin="-76" xMax="580" yMax="759">
+ <contour>
+ <pt x="264" y="759" on="1"/>
+ <pt x="354" y="759" on="1"/>
+ <pt x="354" y="715" on="1"/>
+ <pt x="415" y="709" on="0"/>
+ <pt x="519" y="662" on="0"/>
+ <pt x="562" y="620" on="1"/>
+ <pt x="509" y="548" on="1"/>
+ <pt x="464" y="592" on="0"/>
+ <pt x="370" y="630" on="0"/>
+ <pt x="308" y="630" on="1"/>
+ <pt x="226" y="630" on="0"/>
+ <pt x="137" y="562" on="0"/>
+ <pt x="137" y="502" on="1"/>
+ <pt x="137" y="470" on="0"/>
+ <pt x="160" y="428" on="0"/>
+ <pt x="214" y="402" on="0"/>
+ <pt x="261" y="392" on="1"/>
+ <pt x="360" y="372" on="1"/>
+ <pt x="469" y="350" on="0"/>
+ <pt x="580" y="263" on="0"/>
+ <pt x="580" y="184" on="1"/>
+ <pt x="580" y="102" on="0"/>
+ <pt x="459" y="-8" on="0"/>
+ <pt x="354" y="-18" on="1"/>
+ <pt x="354" y="-76" on="1"/>
+ <pt x="264" y="-76" on="1"/>
+ <pt x="264" y="-18" on="1"/>
+ <pt x="192" y="-12" on="0"/>
+ <pt x="72" y="34" on="0"/>
+ <pt x="29" y="74" on="1"/>
+ <pt x="81" y="146" on="1"/>
+ <pt x="115" y="118" on="0"/>
+ <pt x="180" y="83" on="0"/>
+ <pt x="259" y="67" on="0"/>
+ <pt x="310" y="67" on="1"/>
+ <pt x="403" y="67" on="0"/>
+ <pt x="486" y="128" on="0"/>
+ <pt x="486" y="183" on="1"/>
+ <pt x="486" y="212" on="0"/>
+ <pt x="461" y="251" on="0"/>
+ <pt x="401" y="277" on="0"/>
+ <pt x="348" y="286" on="1"/>
+ <pt x="249" y="304" on="1"/>
+ <pt x="148" y="323" on="0"/>
+ <pt x="43" y="420" on="0"/>
+ <pt x="43" y="502" on="1"/>
+ <pt x="43" y="559" on="0"/>
+ <pt x="99" y="650" on="0"/>
+ <pt x="199" y="707" on="0"/>
+ <pt x="264" y="715" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dollar.bold" xMin="29" yMin="-76" xMax="580" yMax="759">
+ <contour>
+ <pt x="264" y="759" on="1"/>
+ <pt x="354" y="759" on="1"/>
+ <pt x="354" y="715" on="1"/>
+ <pt x="415" y="709" on="0"/>
+ <pt x="519" y="662" on="0"/>
+ <pt x="562" y="620" on="1"/>
+ <pt x="509" y="548" on="1"/>
+ <pt x="464" y="592" on="0"/>
+ <pt x="370" y="630" on="0"/>
+ <pt x="308" y="630" on="1"/>
+ <pt x="226" y="630" on="0"/>
+ <pt x="137" y="562" on="0"/>
+ <pt x="137" y="502" on="1"/>
+ <pt x="137" y="470" on="0"/>
+ <pt x="160" y="428" on="0"/>
+ <pt x="214" y="402" on="0"/>
+ <pt x="261" y="392" on="1"/>
+ <pt x="360" y="372" on="1"/>
+ <pt x="469" y="350" on="0"/>
+ <pt x="580" y="263" on="0"/>
+ <pt x="580" y="184" on="1"/>
+ <pt x="580" y="102" on="0"/>
+ <pt x="459" y="-8" on="0"/>
+ <pt x="354" y="-18" on="1"/>
+ <pt x="354" y="-76" on="1"/>
+ <pt x="264" y="-76" on="1"/>
+ <pt x="264" y="-18" on="1"/>
+ <pt x="192" y="-12" on="0"/>
+ <pt x="72" y="34" on="0"/>
+ <pt x="29" y="74" on="1"/>
+ <pt x="81" y="146" on="1"/>
+ <pt x="115" y="118" on="0"/>
+ <pt x="180" y="83" on="0"/>
+ <pt x="259" y="67" on="0"/>
+ <pt x="310" y="67" on="1"/>
+ <pt x="403" y="67" on="0"/>
+ <pt x="486" y="128" on="0"/>
+ <pt x="486" y="183" on="1"/>
+ <pt x="486" y="212" on="0"/>
+ <pt x="461" y="251" on="0"/>
+ <pt x="401" y="277" on="0"/>
+ <pt x="348" y="286" on="1"/>
+ <pt x="249" y="304" on="1"/>
+ <pt x="148" y="323" on="0"/>
+ <pt x="43" y="420" on="0"/>
+ <pt x="43" y="502" on="1"/>
+ <pt x="43" y="559" on="0"/>
+ <pt x="99" y="650" on="0"/>
+ <pt x="199" y="707" on="0"/>
+ <pt x="264" y="715" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;SimpleTwoAxis-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Simple Two Axis Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SimpleTwoAxis-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.bold"/>
+ <psName name="acutecomb"/>
+ <psName name="dollar.BRACKET.500"/>
+ </extraNames>
+ </post>
+
+ <GDEF>
+ <Version value="0x00010000"/>
+ <GlyphClassDef>
+ <ClassDef glyph="A" class="1"/>
+ <ClassDef glyph="Aacute" class="1"/>
+ <ClassDef glyph="acutecomb" class="3"/>
+ </GlyphClassDef>
+ </GDEF>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=2 -->
+ <FeatureIndex index="0" value="0"/>
+ <FeatureIndex index="1" value="1"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=2 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="kern"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="mark"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ <Glyph value="V"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <!-- PairSetCount=3 -->
+ <PairSet index="0">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="1">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="V"/>
+ <Value1 XAdvance="-80"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="2">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="O"/>
+ <Value1 XAdvance="-20"/>
+ </PairValueRecord>
+ </PairSet>
+ </PairPos>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="4"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MarkBasePos index="0" Format="1">
+ <MarkCoverage>
+ <Glyph value="acutecomb"/>
+ </MarkCoverage>
+ <BaseCoverage>
+ <Glyph value="A"/>
+ <Glyph value="Aacute"/>
+ </BaseCoverage>
+ <!-- ClassCount=1 -->
+ <MarkArray>
+ <!-- MarkCount=1 -->
+ <MarkRecord index="0">
+ <Class value="0"/>
+ <MarkAnchor Format="1">
+ <XCoordinate value="4"/>
+ <YCoordinate value="623"/>
+ </MarkAnchor>
+ </MarkRecord>
+ </MarkArray>
+ <BaseArray>
+ <!-- BaseCount=2 -->
+ <BaseRecord index="0">
+ <BaseAnchor index="0" Format="3">
+ <XCoordinate value="406"/>
+ <YCoordinate value="753"/>
+ </BaseAnchor>
+ </BaseRecord>
+ <BaseRecord index="1">
+ <BaseAnchor index="0" Format="1">
+ <XCoordinate value="406"/>
+ <YCoordinate value="753"/>
+ </BaseAnchor>
+ </BaseRecord>
+ </BaseArray>
+ </MarkBasePos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_kerning_merging/0.ttx b/Tests/varLib/data/master_kerning_merging/0.ttx
index 858f9275..1ca22f68 100644
--- a/Tests/varLib/data/master_kerning_merging/0.ttx
+++ b/Tests/varLib/data/master_kerning_merging/0.ttx
@@ -263,19 +263,19 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="B"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="A" class="1"/>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="C" class="1"/>
</ClassDef2>
<!-- Class1Count=2 -->
diff --git a/Tests/varLib/data/master_kerning_merging/1.ttx b/Tests/varLib/data/master_kerning_merging/1.ttx
index d60a7084..9f6756d8 100644
--- a/Tests/varLib/data/master_kerning_merging/1.ttx
+++ b/Tests/varLib/data/master_kerning_merging/1.ttx
@@ -257,17 +257,17 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="B" class="2"/>
<ClassDef glyph="D" class="1"/>
</ClassDef2>
diff --git a/Tests/varLib/data/master_kerning_merging/2.ttx b/Tests/varLib/data/master_kerning_merging/2.ttx
index e01a7b1f..b8302e8b 100644
--- a/Tests/varLib/data/master_kerning_merging/2.ttx
+++ b/Tests/varLib/data/master_kerning_merging/2.ttx
@@ -263,19 +263,19 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="B"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="A" class="1"/>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="D" class="1"/>
</ClassDef2>
<!-- Class1Count=2 -->
diff --git a/Tests/varLib/data/master_ttx_interpolatable_otf/TestFamily2-Master0.ttx b/Tests/varLib/data/master_ttx_interpolatable_otf/TestFamily2-Master0.ttx
index a6a8e003..157043f0 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_otf/TestFamily2-Master0.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_otf/TestFamily2-Master0.ttx
@@ -766,7 +766,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="A" out="A.sc"/>
</SingleSubst>
</Lookup>
@@ -774,7 +774,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="a" out="a.alt"/>
</SingleSubst>
</Lookup>
@@ -782,7 +782,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="ampersand" out="a,n,d"/>
</MultipleSubst>
</Lookup>
@@ -790,7 +790,7 @@
<LookupType value="3"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <AlternateSubst index="0" Format="1">
+ <AlternateSubst index="0">
<AlternateSet glyph="a">
<Alternate glyph="a.alt"/>
<Alternate glyph="A.sc"/>
@@ -801,7 +801,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="f">
<Ligature components="t" glyph="f_t"/>
</LigatureSet>
@@ -814,11 +814,11 @@
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=0 -->
<!-- InputGlyphCount=1 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="a"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="t"/>
</LookAheadCoverage>
<!-- SubstCount=1 -->
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Bold.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Bold.ttx
index 55d686e3..cf4b5dae 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Bold.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Bold.ttx
@@ -305,7 +305,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="dotabovecomb" class="3"/>
<ClassDef glyph="e" class="1"/>
</GlyphClassDef>
@@ -344,10 +344,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="dotabovecomb"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="e"/>
</BaseCoverage>
<!-- ClassCount=1 -->
@@ -407,7 +407,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="a">
<Ligature components="e,s,s" glyph="s"/>
</LigatureSet>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Regular.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Regular.ttx
index e013e0b7..c93da2a8 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Regular.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/SparseMasters-Regular.ttx
@@ -305,7 +305,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="dotabovecomb" class="3"/>
<ClassDef glyph="e" class="1"/>
</GlyphClassDef>
@@ -344,10 +344,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="dotabovecomb"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="e"/>
</BaseCoverage>
<!-- ClassCount=1 -->
@@ -407,7 +407,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="a">
<Ligature components="e,s,s" glyph="s"/>
</LigatureSet>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master0.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master0.ttx
index 6054e4bd..ca5a2e15 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master0.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master0.ttx
@@ -519,7 +519,7 @@
<GDEF>
<Version value="0x00010003"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="uni0024" class="1"/>
<ClassDef glyph="uni0024.nostroke" class="1"/>
<ClassDef glyph="uni0041" class="1"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master1.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master1.ttx
index afd61de9..9076cf7e 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master1.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master1.ttx
@@ -519,7 +519,7 @@
<GDEF>
<Version value="0x00010003"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="uni0024" class="1"/>
<ClassDef glyph="uni0024.nostroke" class="1"/>
<ClassDef glyph="uni0041" class="1"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master2.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master2.ttx
index 0ed2f4ac..9bec8c04 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master2.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master2.ttx
@@ -503,7 +503,7 @@
<GDEF>
<Version value="0x00010003"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="uni0024" class="1"/>
<ClassDef glyph="uni0024.nostroke" class="1"/>
<ClassDef glyph="uni0041" class="1"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master3.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master3.ttx
index 5666541e..1cfdfd91 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master3.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master3.ttx
@@ -503,7 +503,7 @@
<GDEF>
<Version value="0x00010003"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="uni0024" class="1"/>
<ClassDef glyph="uni0024.nostroke" class="1"/>
<ClassDef glyph="uni0041" class="1"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master4.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master4.ttx
index 87381873..1ae5d473 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master4.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily-Master4.ttx
@@ -503,7 +503,7 @@
<GDEF>
<Version value="0x00010003"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="uni0024" class="1"/>
<ClassDef glyph="uni0024.nostroke" class="1"/>
<ClassDef glyph="uni0041" class="1"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily2-Master0.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily2-Master0.ttx
index 13d48e74..d1a33935 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily2-Master0.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily2-Master0.ttx
@@ -1081,7 +1081,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="A" out="A.sc"/>
</SingleSubst>
</Lookup>
@@ -1089,7 +1089,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="a" out="a.alt"/>
</SingleSubst>
</Lookup>
@@ -1097,7 +1097,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="ampersand" out="a,n,d"/>
</MultipleSubst>
</Lookup>
@@ -1105,7 +1105,7 @@
<LookupType value="3"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <AlternateSubst index="0" Format="1">
+ <AlternateSubst index="0">
<AlternateSet glyph="a">
<Alternate glyph="a.alt"/>
<Alternate glyph="A.sc"/>
@@ -1116,7 +1116,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="f">
<Ligature components="t" glyph="f_t"/>
</LigatureSet>
@@ -1129,11 +1129,11 @@
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=0 -->
<!-- InputGlyphCount=1 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="a"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="t"/>
</LookAheadCoverage>
<!-- SubstCount=1 -->
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Bold.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Bold.ttx
index a752bc17..0f9e97a5 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Bold.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Bold.ttx
@@ -477,17 +477,17 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="T"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="T" class="4"/>
<ClassDef glyph="n" class="3"/>
<ClassDef glyph="o" class="2"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Condensed.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Condensed.ttx
index db0372ab..f8ff9878 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Condensed.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Condensed.ttx
@@ -483,17 +483,17 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="T"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="T" class="4"/>
<ClassDef glyph="n" class="3"/>
<ClassDef glyph="o" class="2"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedBold.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedBold.ttx
index 3313ce6d..2b7264d1 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedBold.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedBold.ttx
@@ -483,17 +483,17 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="T"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="T" class="4"/>
<ClassDef glyph="n" class="3"/>
<ClassDef glyph="o" class="2"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedLight.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedLight.ttx
index c83912b2..d03abf5f 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedLight.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedLight.ttx
@@ -483,17 +483,17 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="T"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="T" class="4"/>
<ClassDef glyph="n" class="3"/>
<ClassDef glyph="o" class="2"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedSemiBold.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedSemiBold.ttx
index 7b0c88f8..11892209 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedSemiBold.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-CondensedSemiBold.ttx
@@ -483,17 +483,17 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="T"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="T" class="4"/>
<ClassDef glyph="n" class="3"/>
<ClassDef glyph="o" class="2"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Light.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Light.ttx
index 0e84719d..3583c0d3 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Light.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Light.ttx
@@ -483,17 +483,17 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="T"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="T" class="4"/>
<ClassDef glyph="n" class="3"/>
<ClassDef glyph="o" class="2"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Regular.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Regular.ttx
index 44848db2..bf68b167 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Regular.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-Regular.ttx
@@ -477,17 +477,17 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="T"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="T" class="4"/>
<ClassDef glyph="n" class="3"/>
<ClassDef glyph="o" class="2"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-SemiBold.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-SemiBold.ttx
index 7e6a0b32..96badb34 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-SemiBold.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily3-SemiBold.ttx
@@ -483,17 +483,17 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="T"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="T" class="4"/>
<ClassDef glyph="n" class="3"/>
<ClassDef glyph="o" class="2"/>
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Italic15.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Italic15.ttx
index f7aa15fc..ab5789e5 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Italic15.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Italic15.ttx
@@ -455,7 +455,7 @@
<GDEF>
<Version value="0x00010002"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="N" class="1"/>
<ClassDef glyph="O" class="1"/>
<ClassDef glyph="Odieresis" class="1"/>
@@ -467,7 +467,7 @@
<MarkGlyphSetsDef>
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=1 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="uni0308"/>
</Coverage>
</MarkGlyphSetsDef>
@@ -545,7 +545,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="N"/>
<Glyph value="O"/>
<Glyph value="Odieresis"/>
@@ -559,10 +559,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="uni0308"/>
</MarkCoverage>
- <BaseCoverage Format="2">
+ <BaseCoverage>
<Glyph value="N"/>
<Glyph value="O"/>
<Glyph value="Odieresis"/>
@@ -624,13 +624,13 @@
</Lookup>
<Lookup index="2">
<LookupType value="6"/>
- <LookupFlag value="16"/>
+ <LookupFlag value="16"/><!-- useMarkFilteringSet -->
<!-- SubTableCount=1 -->
<MarkMarkPos index="0" Format="1">
- <Mark1Coverage Format="1">
+ <Mark1Coverage>
<Glyph value="uni0308"/>
</Mark1Coverage>
- <Mark2Coverage Format="1">
+ <Mark2Coverage>
<Glyph value="uni0308"/>
</Mark2Coverage>
<!-- ClassCount=1 -->
diff --git a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Regular.ttx b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Regular.ttx
index 2e354b0a..0b7063fb 100644
--- a/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Regular.ttx
+++ b/Tests/varLib/data/master_ttx_interpolatable_ttf/TestFamily4-Regular.ttx
@@ -449,7 +449,7 @@
<GDEF>
<Version value="0x00010002"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="N" class="1"/>
<ClassDef glyph="O" class="1"/>
<ClassDef glyph="Odieresis" class="1"/>
@@ -461,7 +461,7 @@
<MarkGlyphSetsDef>
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=1 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
<Glyph value="uni0308"/>
</Coverage>
</MarkGlyphSetsDef>
@@ -539,7 +539,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="N"/>
<Glyph value="O"/>
<Glyph value="Odieresis"/>
@@ -553,10 +553,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="uni0308"/>
</MarkCoverage>
- <BaseCoverage Format="2">
+ <BaseCoverage>
<Glyph value="N"/>
<Glyph value="O"/>
<Glyph value="Odieresis"/>
@@ -618,13 +618,13 @@
</Lookup>
<Lookup index="2">
<LookupType value="6"/>
- <LookupFlag value="16"/>
+ <LookupFlag value="16"/><!-- useMarkFilteringSet -->
<!-- SubTableCount=1 -->
<MarkMarkPos index="0" Format="1">
- <Mark1Coverage Format="1">
+ <Mark1Coverage>
<Glyph value="uni0308"/>
</Mark1Coverage>
- <Mark2Coverage Format="1">
+ <Mark2Coverage>
<Glyph value="uni0308"/>
</Mark2Coverage>
<!-- ClassCount=1 -->
diff --git a/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx b/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx
index c2a718ec..29c5bb31 100644
--- a/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx
+++ b/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx
@@ -623,7 +623,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="dollar" out="glyph00003"/>
</SingleSubst>
</Lookup>
@@ -631,7 +631,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="dollar" out="glyph00003"/>
</SingleSubst>
</Lookup>
diff --git a/Tests/varLib/data/master_vpal_test/master_vpal_test_0.ttx b/Tests/varLib/data/master_vpal_test/master_vpal_test_0.ttx
index 1fbb45d3..cd454b85 100644
--- a/Tests/varLib/data/master_vpal_test/master_vpal_test_0.ttx
+++ b/Tests/varLib/data/master_vpal_test/master_vpal_test_0.ttx
@@ -424,7 +424,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=5 -->
<SinglePos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uni3001"/>
<Glyph value="uniFF1A"/>
</Coverage>
@@ -434,7 +434,7 @@
<Value index="1" XPlacement="-250" XAdvance="-500"/>
</SinglePos>
<SinglePos index="1" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uni30FB"/>
</Coverage>
<ValueFormat value="7"/>
@@ -442,7 +442,7 @@
<Value index="0" XPlacement="-250" YPlacement="1" XAdvance="-500"/>
</SinglePos>
<SinglePos index="2" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uni3073"/>
<Glyph value="uni3074"/>
</Coverage>
@@ -452,7 +452,7 @@
<Value index="1" XAdvance="-30"/>
</SinglePos>
<SinglePos index="3" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uni307B"/>
</Coverage>
<ValueFormat value="1"/>
@@ -460,7 +460,7 @@
<Value index="0" XPlacement="11"/>
</SinglePos>
<SinglePos index="4" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uniFF2D"/>
</Coverage>
<ValueFormat value="5"/>
diff --git a/Tests/varLib/data/master_vpal_test/master_vpal_test_1.ttx b/Tests/varLib/data/master_vpal_test/master_vpal_test_1.ttx
index bb439586..e1b77c84 100644
--- a/Tests/varLib/data/master_vpal_test/master_vpal_test_1.ttx
+++ b/Tests/varLib/data/master_vpal_test/master_vpal_test_1.ttx
@@ -424,7 +424,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=3 -->
<SinglePos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uni3001"/>
<Glyph value="uni30FB"/>
<Glyph value="uniFF1A"/>
@@ -436,7 +436,7 @@
<Value index="2" XPlacement="-250" XAdvance="-500"/>
</SinglePos>
<SinglePos index="1" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uni3073"/>
<Glyph value="uni3074"/>
</Coverage>
@@ -446,7 +446,7 @@
<Value index="1" XAdvance="-30"/>
</SinglePos>
<SinglePos index="2" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uniFF2D"/>
<Glyph value="uni307B"/>
</Coverage>
diff --git a/Tests/varLib/data/master_vvar_cff2/TestVVAR.0.ttx b/Tests/varLib/data/master_vvar_cff2/TestVVAR.0.ttx
index 7383c171..d956d8c4 100644
--- a/Tests/varLib/data/master_vvar_cff2/TestVVAR.0.ttx
+++ b/Tests/varLib/data/master_vvar_cff2/TestVVAR.0.ttx
@@ -705,7 +705,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uni3042" out="c30843"/>
<Substitution in="uni56FD" out="c32051"/>
<Substitution in="uni6280" out="c31621"/>
@@ -718,7 +718,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uni3042" out="c30843"/>
<Substitution in="uni56FD" out="c32051"/>
<Substitution in="uni6280" out="c31621"/>
diff --git a/Tests/varLib/data/master_vvar_cff2/TestVVAR.1.ttx b/Tests/varLib/data/master_vvar_cff2/TestVVAR.1.ttx
index f6020977..9f677494 100644
--- a/Tests/varLib/data/master_vvar_cff2/TestVVAR.1.ttx
+++ b/Tests/varLib/data/master_vvar_cff2/TestVVAR.1.ttx
@@ -705,7 +705,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uni3042" out="c30843"/>
<Substitution in="uni56FD" out="c32051"/>
<Substitution in="uni6280" out="c31621"/>
@@ -718,7 +718,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uni3042" out="c30843"/>
<Substitution in="uni56FD" out="c32051"/>
<Substitution in="uni6280" out="c31621"/>
diff --git a/Tests/varLib/data/test_results/Build.ttx b/Tests/varLib/data/test_results/Build.ttx
index 6e9c6e37..c802bf32 100644
--- a/Tests/varLib/data/test_results/Build.ttx
+++ b/Tests/varLib/data/test_results/Build.ttx
@@ -3,7 +3,7 @@
<GDEF>
<Version value="0x00010003"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="uni0024" class="1"/>
<ClassDef glyph="uni0024.nostroke" class="1"/>
<ClassDef glyph="uni0041" class="1"/>
@@ -405,509 +405,6 @@
<delta pt="3" x="0" y="0"/>
</tuple>
</glyphVariations>
- <glyphVariations glyph="uni0041">
- <tuple>
- <coord axis="wght" value="-1.0"/>
- <delta pt="0" x="7" y="0"/>
- <delta pt="1" x="7" y="-20"/>
- <delta pt="2" x="-6" y="-29"/>
- <delta pt="3" x="-12" y="-29"/>
- <delta pt="4" x="-25" y="-20"/>
- <delta pt="5" x="-25" y="0"/>
- <delta pt="6" x="14" y="0"/>
- <delta pt="7" x="4" y="9"/>
- <delta pt="8" x="-36" y="9"/>
- <delta pt="9" x="-37" y="0"/>
- <delta pt="10" x="24" y="0"/>
- <delta pt="11" x="9" y="58"/>
- <delta pt="12" x="3" y="68"/>
- <delta pt="13" x="-4" y="0"/>
- <delta pt="14" x="3" y="28"/>
- <delta pt="15" x="-4" y="2"/>
- <delta pt="16" x="4" y="2"/>
- <delta pt="17" x="-4" y="28"/>
- <delta pt="18" x="20" y="0"/>
- <delta pt="19" x="20" y="-20"/>
- <delta pt="20" x="14" y="-29"/>
- <delta pt="21" x="8" y="-29"/>
- <delta pt="22" x="-2" y="-20"/>
- <delta pt="23" x="-2" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="-10" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="1.0"/>
- <delta pt="0" x="5" y="0"/>
- <delta pt="1" x="5" y="19"/>
- <delta pt="2" x="9" y="19"/>
- <delta pt="3" x="6" y="19"/>
- <delta pt="4" x="-15" y="19"/>
- <delta pt="5" x="-15" y="0"/>
- <delta pt="6" x="-6" y="0"/>
- <delta pt="7" x="-14" y="-23"/>
- <delta pt="8" x="46" y="-23"/>
- <delta pt="9" x="39" y="0"/>
- <delta pt="10" x="-69" y="0"/>
- <delta pt="11" x="-27" y="-86"/>
- <delta pt="12" x="-7" y="-16"/>
- <delta pt="13" x="11" y="0"/>
- <delta pt="14" x="-2" y="-39"/>
- <delta pt="15" x="-1" y="-22"/>
- <delta pt="16" x="-1" y="-22"/>
- <delta pt="17" x="8" y="-39"/>
- <delta pt="18" x="-41" y="0"/>
- <delta pt="19" x="-41" y="16"/>
- <delta pt="20" x="-59" y="16"/>
- <delta pt="21" x="6" y="16"/>
- <delta pt="22" x="12" y="16"/>
- <delta pt="23" x="12" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="17" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="2" y="0"/>
- <delta pt="1" x="2" y="-9"/>
- <delta pt="2" x="-4" y="-9"/>
- <delta pt="3" x="-4" y="-9"/>
- <delta pt="4" x="-2" y="-9"/>
- <delta pt="5" x="-2" y="0"/>
- <delta pt="6" x="2" y="0"/>
- <delta pt="7" x="-4" y="0"/>
- <delta pt="8" x="-4" y="0"/>
- <delta pt="9" x="-4" y="0"/>
- <delta pt="10" x="-4" y="0"/>
- <delta pt="11" x="-6" y="8"/>
- <delta pt="12" x="-10" y="0"/>
- <delta pt="13" x="-2" y="0"/>
- <delta pt="14" x="0" y="5"/>
- <delta pt="15" x="-3" y="-5"/>
- <delta pt="16" x="5" y="-5"/>
- <delta pt="17" x="-1" y="5"/>
- <delta pt="18" x="0" y="0"/>
- <delta pt="19" x="0" y="-8"/>
- <delta pt="20" x="4" y="-8"/>
- <delta pt="21" x="0" y="-8"/>
- <delta pt="22" x="0" y="-8"/>
- <delta pt="23" x="0" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="-1.0"/>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="-2" y="0"/>
- <delta pt="1" x="-2" y="9"/>
- <delta pt="2" x="4" y="9"/>
- <delta pt="3" x="4" y="9"/>
- <delta pt="4" x="2" y="9"/>
- <delta pt="5" x="2" y="0"/>
- <delta pt="6" x="-2" y="0"/>
- <delta pt="7" x="4" y="0"/>
- <delta pt="8" x="4" y="0"/>
- <delta pt="9" x="4" y="0"/>
- <delta pt="10" x="4" y="0"/>
- <delta pt="11" x="6" y="-8"/>
- <delta pt="12" x="10" y="0"/>
- <delta pt="13" x="2" y="0"/>
- <delta pt="14" x="0" y="-5"/>
- <delta pt="15" x="3" y="5"/>
- <delta pt="16" x="-5" y="5"/>
- <delta pt="17" x="1" y="-5"/>
- <delta pt="18" x="0" y="0"/>
- <delta pt="19" x="0" y="8"/>
- <delta pt="20" x="-4" y="8"/>
- <delta pt="21" x="0" y="8"/>
- <delta pt="22" x="0" y="8"/>
- <delta pt="23" x="0" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="1.0"/>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="3" y="0"/>
- <delta pt="1" x="3" y="-15"/>
- <delta pt="2" x="-6" y="-15"/>
- <delta pt="3" x="-6" y="-15"/>
- <delta pt="4" x="-3" y="-15"/>
- <delta pt="5" x="-3" y="0"/>
- <delta pt="6" x="3" y="0"/>
- <delta pt="7" x="-6" y="0"/>
- <delta pt="8" x="-6" y="0"/>
- <delta pt="9" x="-6" y="0"/>
- <delta pt="10" x="-6" y="0"/>
- <delta pt="11" x="-11" y="13"/>
- <delta pt="12" x="-17" y="0"/>
- <delta pt="13" x="-3" y="0"/>
- <delta pt="14" x="-1" y="8"/>
- <delta pt="15" x="-5" y="-9"/>
- <delta pt="16" x="8" y="-9"/>
- <delta pt="17" x="-1" y="8"/>
- <delta pt="18" x="0" y="0"/>
- <delta pt="19" x="0" y="-13"/>
- <delta pt="20" x="6" y="-13"/>
- <delta pt="21" x="0" y="-13"/>
- <delta pt="22" x="0" y="-13"/>
- <delta pt="23" x="0" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="0"/>
- </tuple>
- </glyphVariations>
- <glyphVariations glyph="uni0061">
- <tuple>
- <coord axis="wght" value="-1.0"/>
- <delta pt="0" x="11" y="-8"/>
- <delta pt="1" x="11" y="4"/>
- <delta pt="2" x="22" y="5"/>
- <delta pt="3" x="-4" y="-8"/>
- <delta pt="4" x="6" y="-5"/>
- <delta pt="5" x="3" y="-11"/>
- <delta pt="6" x="4" y="-9"/>
- <delta pt="7" x="4" y="9"/>
- <delta pt="8" x="0" y="7"/>
- <delta pt="9" x="-9" y="8"/>
- <delta pt="10" x="-24" y="3"/>
- <delta pt="11" x="-18" y="6"/>
- <delta pt="12" x="-44" y="1"/>
- <delta pt="13" x="-44" y="-16"/>
- <delta pt="14" x="-44" y="-22"/>
- <delta pt="15" x="-36" y="-39"/>
- <delta pt="16" x="-24" y="-39"/>
- <delta pt="17" x="-7" y="-39"/>
- <delta pt="18" x="26" y="-15"/>
- <delta pt="19" x="26" y="3"/>
- <delta pt="20" x="17" y="0"/>
- <delta pt="21" x="3" y="-4"/>
- <delta pt="22" x="23" y="15"/>
- <delta pt="23" x="22" y="8"/>
- <delta pt="24" x="6" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="2" y="0"/>
- <delta pt="27" x="11" y="-2"/>
- <delta pt="28" x="30" y="7"/>
- <delta pt="29" x="30" y="4"/>
- <delta pt="30" x="30" y="13"/>
- <delta pt="31" x="14" y="21"/>
- <delta pt="32" x="3" y="21"/>
- <delta pt="33" x="-15" y="21"/>
- <delta pt="34" x="-32" y="5"/>
- <delta pt="35" x="-34" y="-9"/>
- <delta pt="36" x="-48" y="-14"/>
- <delta pt="37" x="-40" y="4"/>
- <delta pt="38" x="-36" y="14"/>
- <delta pt="39" x="-24" y="27"/>
- <delta pt="40" x="-13" y="27"/>
- <delta pt="41" x="12" y="27"/>
- <delta pt="42" x="10" y="6"/>
- <delta pt="43" x="12" y="5"/>
- <delta pt="44" x="-4" y="-4"/>
- <delta pt="45" x="-16" y="-4"/>
- <delta pt="46" x="-20" y="-4"/>
- <delta pt="47" x="-22" y="7"/>
- <delta pt="48" x="-22" y="25"/>
- <delta pt="49" x="-22" y="10"/>
- <delta pt="50" x="-22" y="-15"/>
- <delta pt="51" x="-16" y="-30"/>
- <delta pt="52" x="-9" y="-30"/>
- <delta pt="53" x="-12" y="-30"/>
- <delta pt="54" x="-11" y="-35"/>
- <delta pt="55" x="-5" y="-35"/>
- <delta pt="56" x="-15" y="-27"/>
- <delta pt="57" x="-10" y="-3"/>
- <delta pt="58" x="9" y="-3"/>
- <delta pt="59" x="14" y="-3"/>
- <delta pt="60" x="33" y="-1"/>
- <delta pt="61" x="0" y="0"/>
- <delta pt="62" x="-3" y="0"/>
- <delta pt="63" x="0" y="0"/>
- <delta pt="64" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="1.0"/>
- <delta pt="0" x="-21" y="1"/>
- <delta pt="1" x="-21" y="17"/>
- <delta pt="2" x="-2" y="28"/>
- <delta pt="3" x="20" y="23"/>
- <delta pt="4" x="19" y="20"/>
- <delta pt="5" x="28" y="21"/>
- <delta pt="6" x="26" y="23"/>
- <delta pt="7" x="26" y="15"/>
- <delta pt="8" x="24" y="12"/>
- <delta pt="9" x="30" y="17"/>
- <delta pt="10" x="31" y="15"/>
- <delta pt="11" x="77" y="31"/>
- <delta pt="12" x="66" y="36"/>
- <delta pt="13" x="66" y="18"/>
- <delta pt="14" x="66" y="21"/>
- <delta pt="15" x="49" y="19"/>
- <delta pt="16" x="37" y="19"/>
- <delta pt="17" x="21" y="19"/>
- <delta pt="18" x="-2" y="5"/>
- <delta pt="19" x="-34" y="-18"/>
- <delta pt="20" x="-6" y="3"/>
- <delta pt="21" x="-11" y="12"/>
- <delta pt="22" x="-29" y="-11"/>
- <delta pt="23" x="-17" y="-2"/>
- <delta pt="24" x="-13" y="-3"/>
- <delta pt="25" x="-25" y="-3"/>
- <delta pt="26" x="-29" y="-3"/>
- <delta pt="27" x="-21" y="2"/>
- <delta pt="28" x="-34" y="-14"/>
- <delta pt="29" x="-34" y="17"/>
- <delta pt="30" x="-34" y="7"/>
- <delta pt="31" x="-18" y="7"/>
- <delta pt="32" x="-16" y="7"/>
- <delta pt="33" x="-18" y="7"/>
- <delta pt="34" x="-15" y="9"/>
- <delta pt="35" x="-21" y="12"/>
- <delta pt="36" x="19" y="23"/>
- <delta pt="37" x="45" y="46"/>
- <delta pt="38" x="52" y="7"/>
- <delta pt="39" x="26" y="-21"/>
- <delta pt="40" x="14" y="-21"/>
- <delta pt="41" x="-5" y="-21"/>
- <delta pt="42" x="-17" y="-7"/>
- <delta pt="43" x="-31" y="1"/>
- <delta pt="44" x="-12" y="16"/>
- <delta pt="45" x="34" y="16"/>
- <delta pt="46" x="61" y="16"/>
- <delta pt="47" x="70" y="4"/>
- <delta pt="48" x="70" y="-5"/>
- <delta pt="49" x="70" y="-22"/>
- <delta pt="50" x="70" y="4"/>
- <delta pt="51" x="59" y="22"/>
- <delta pt="52" x="50" y="22"/>
- <delta pt="53" x="43" y="22"/>
- <delta pt="54" x="37" y="19"/>
- <delta pt="55" x="38" y="22"/>
- <delta pt="56" x="47" y="28"/>
- <delta pt="57" x="46" y="-6"/>
- <delta pt="58" x="-2" y="-6"/>
- <delta pt="59" x="-16" y="-6"/>
- <delta pt="60" x="-25" y="-13"/>
- <delta pt="61" x="0" y="0"/>
- <delta pt="62" x="32" y="0"/>
- <delta pt="63" x="0" y="0"/>
- <delta pt="64" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="0" y="-3"/>
- <delta pt="1" x="0" y="-1"/>
- <delta pt="2" x="0" y="-3"/>
- <delta pt="3" x="0" y="-3"/>
- <delta pt="4" x="0" y="-3"/>
- <delta pt="5" x="0" y="-3"/>
- <delta pt="6" x="0" y="-3"/>
- <delta pt="7" x="0" y="4"/>
- <delta pt="8" x="0" y="4"/>
- <delta pt="9" x="2" y="5"/>
- <delta pt="10" x="6" y="7"/>
- <delta pt="11" x="1" y="5"/>
- <delta pt="12" x="0" y="-1"/>
- <delta pt="13" x="0" y="-6"/>
- <delta pt="14" x="0" y="-6"/>
- <delta pt="15" x="-1" y="-6"/>
- <delta pt="16" x="0" y="-6"/>
- <delta pt="17" x="0" y="-6"/>
- <delta pt="18" x="0" y="-5"/>
- <delta pt="19" x="0" y="-4"/>
- <delta pt="20" x="0" y="-1"/>
- <delta pt="21" x="0" y="0"/>
- <delta pt="22" x="0" y="0"/>
- <delta pt="23" x="0" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="-1"/>
- <delta pt="28" x="0" y="-2"/>
- <delta pt="29" x="0" y="7"/>
- <delta pt="30" x="0" y="6"/>
- <delta pt="31" x="0" y="7"/>
- <delta pt="32" x="0" y="7"/>
- <delta pt="33" x="0" y="7"/>
- <delta pt="34" x="0" y="7"/>
- <delta pt="35" x="0" y="7"/>
- <delta pt="36" x="0" y="0"/>
- <delta pt="37" x="0" y="0"/>
- <delta pt="38" x="0" y="0"/>
- <delta pt="39" x="0" y="0"/>
- <delta pt="40" x="0" y="0"/>
- <delta pt="41" x="0" y="0"/>
- <delta pt="42" x="0" y="0"/>
- <delta pt="43" x="0" y="0"/>
- <delta pt="44" x="0" y="0"/>
- <delta pt="45" x="0" y="0"/>
- <delta pt="46" x="0" y="0"/>
- <delta pt="47" x="0" y="0"/>
- <delta pt="48" x="0" y="0"/>
- <delta pt="49" x="0" y="-6"/>
- <delta pt="50" x="0" y="-7"/>
- <delta pt="51" x="0" y="-8"/>
- <delta pt="52" x="0" y="-8"/>
- <delta pt="53" x="1" y="-8"/>
- <delta pt="54" x="2" y="-5"/>
- <delta pt="55" x="4" y="-2"/>
- <delta pt="56" x="0" y="0"/>
- <delta pt="57" x="0" y="0"/>
- <delta pt="58" x="0" y="0"/>
- <delta pt="59" x="0" y="0"/>
- <delta pt="60" x="0" y="-1"/>
- <delta pt="61" x="0" y="0"/>
- <delta pt="62" x="0" y="0"/>
- <delta pt="63" x="0" y="0"/>
- <delta pt="64" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="-1.0"/>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="0" y="3"/>
- <delta pt="1" x="0" y="1"/>
- <delta pt="2" x="0" y="3"/>
- <delta pt="3" x="0" y="3"/>
- <delta pt="4" x="0" y="3"/>
- <delta pt="5" x="0" y="3"/>
- <delta pt="6" x="0" y="3"/>
- <delta pt="7" x="0" y="-4"/>
- <delta pt="8" x="0" y="-4"/>
- <delta pt="9" x="-2" y="-5"/>
- <delta pt="10" x="-6" y="-7"/>
- <delta pt="11" x="-1" y="-5"/>
- <delta pt="12" x="0" y="1"/>
- <delta pt="13" x="0" y="6"/>
- <delta pt="14" x="0" y="6"/>
- <delta pt="15" x="1" y="6"/>
- <delta pt="16" x="0" y="6"/>
- <delta pt="17" x="0" y="6"/>
- <delta pt="18" x="0" y="5"/>
- <delta pt="19" x="0" y="4"/>
- <delta pt="20" x="0" y="1"/>
- <delta pt="21" x="0" y="0"/>
- <delta pt="22" x="0" y="0"/>
- <delta pt="23" x="0" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="1"/>
- <delta pt="28" x="0" y="2"/>
- <delta pt="29" x="0" y="-7"/>
- <delta pt="30" x="0" y="-6"/>
- <delta pt="31" x="0" y="-7"/>
- <delta pt="32" x="0" y="-7"/>
- <delta pt="33" x="0" y="-7"/>
- <delta pt="34" x="0" y="-7"/>
- <delta pt="35" x="0" y="-7"/>
- <delta pt="36" x="0" y="0"/>
- <delta pt="37" x="0" y="0"/>
- <delta pt="38" x="0" y="0"/>
- <delta pt="39" x="0" y="0"/>
- <delta pt="40" x="0" y="0"/>
- <delta pt="41" x="0" y="0"/>
- <delta pt="42" x="0" y="0"/>
- <delta pt="43" x="0" y="0"/>
- <delta pt="44" x="0" y="0"/>
- <delta pt="45" x="0" y="0"/>
- <delta pt="46" x="0" y="0"/>
- <delta pt="47" x="0" y="0"/>
- <delta pt="48" x="0" y="0"/>
- <delta pt="49" x="0" y="6"/>
- <delta pt="50" x="0" y="7"/>
- <delta pt="51" x="0" y="8"/>
- <delta pt="52" x="0" y="8"/>
- <delta pt="53" x="-1" y="8"/>
- <delta pt="54" x="-2" y="5"/>
- <delta pt="55" x="-4" y="2"/>
- <delta pt="56" x="0" y="0"/>
- <delta pt="57" x="0" y="0"/>
- <delta pt="58" x="0" y="0"/>
- <delta pt="59" x="0" y="0"/>
- <delta pt="60" x="0" y="1"/>
- <delta pt="61" x="0" y="0"/>
- <delta pt="62" x="0" y="0"/>
- <delta pt="63" x="0" y="0"/>
- <delta pt="64" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="1.0"/>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="0" y="-5"/>
- <delta pt="1" x="0" y="0"/>
- <delta pt="2" x="3" y="-4"/>
- <delta pt="3" x="0" y="-4"/>
- <delta pt="4" x="0" y="-4"/>
- <delta pt="5" x="0" y="-4"/>
- <delta pt="6" x="0" y="-4"/>
- <delta pt="7" x="0" y="8"/>
- <delta pt="8" x="0" y="8"/>
- <delta pt="9" x="5" y="9"/>
- <delta pt="10" x="11" y="13"/>
- <delta pt="11" x="2" y="10"/>
- <delta pt="12" x="0" y="0"/>
- <delta pt="13" x="0" y="-9"/>
- <delta pt="14" x="0" y="-9"/>
- <delta pt="15" x="-1" y="-9"/>
- <delta pt="16" x="0" y="-9"/>
- <delta pt="17" x="0" y="-9"/>
- <delta pt="18" x="0" y="-10"/>
- <delta pt="19" x="0" y="-8"/>
- <delta pt="20" x="0" y="-2"/>
- <delta pt="21" x="0" y="1"/>
- <delta pt="22" x="0" y="0"/>
- <delta pt="23" x="1" y="-1"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="-1"/>
- <delta pt="28" x="0" y="-4"/>
- <delta pt="29" x="0" y="12"/>
- <delta pt="30" x="0" y="13"/>
- <delta pt="31" x="0" y="13"/>
- <delta pt="32" x="0" y="13"/>
- <delta pt="33" x="0" y="13"/>
- <delta pt="34" x="0" y="13"/>
- <delta pt="35" x="0" y="13"/>
- <delta pt="36" x="0" y="0"/>
- <delta pt="37" x="0" y="0"/>
- <delta pt="38" x="0" y="0"/>
- <delta pt="39" x="0" y="1"/>
- <delta pt="40" x="0" y="1"/>
- <delta pt="41" x="0" y="1"/>
- <delta pt="42" x="0" y="1"/>
- <delta pt="43" x="0" y="0"/>
- <delta pt="44" x="0" y="0"/>
- <delta pt="45" x="0" y="0"/>
- <delta pt="46" x="0" y="0"/>
- <delta pt="47" x="0" y="-1"/>
- <delta pt="48" x="0" y="-1"/>
- <delta pt="49" x="0" y="-9"/>
- <delta pt="50" x="0" y="-13"/>
- <delta pt="51" x="1" y="-14"/>
- <delta pt="52" x="1" y="-14"/>
- <delta pt="53" x="2" y="-14"/>
- <delta pt="54" x="5" y="-11"/>
- <delta pt="55" x="7" y="-4"/>
- <delta pt="56" x="0" y="0"/>
- <delta pt="57" x="0" y="0"/>
- <delta pt="58" x="0" y="0"/>
- <delta pt="59" x="0" y="0"/>
- <delta pt="60" x="1" y="0"/>
- <delta pt="61" x="0" y="0"/>
- <delta pt="62" x="0" y="0"/>
- <delta pt="63" x="0" y="0"/>
- <delta pt="64" x="0" y="0"/>
- </tuple>
- </glyphVariations>
<glyphVariations glyph="uni0024">
<tuple>
<coord axis="wght" value="-1.0"/>
@@ -1606,6 +1103,509 @@
<delta pt="65" x="0" y="0"/>
</tuple>
</glyphVariations>
+ <glyphVariations glyph="uni0041">
+ <tuple>
+ <coord axis="wght" value="-1.0"/>
+ <delta pt="0" x="7" y="0"/>
+ <delta pt="1" x="7" y="-20"/>
+ <delta pt="2" x="-6" y="-29"/>
+ <delta pt="3" x="-12" y="-29"/>
+ <delta pt="4" x="-25" y="-20"/>
+ <delta pt="5" x="-25" y="0"/>
+ <delta pt="6" x="14" y="0"/>
+ <delta pt="7" x="4" y="9"/>
+ <delta pt="8" x="-36" y="9"/>
+ <delta pt="9" x="-37" y="0"/>
+ <delta pt="10" x="24" y="0"/>
+ <delta pt="11" x="9" y="58"/>
+ <delta pt="12" x="3" y="68"/>
+ <delta pt="13" x="-4" y="0"/>
+ <delta pt="14" x="3" y="28"/>
+ <delta pt="15" x="-4" y="2"/>
+ <delta pt="16" x="4" y="2"/>
+ <delta pt="17" x="-4" y="28"/>
+ <delta pt="18" x="20" y="0"/>
+ <delta pt="19" x="20" y="-20"/>
+ <delta pt="20" x="14" y="-29"/>
+ <delta pt="21" x="8" y="-29"/>
+ <delta pt="22" x="-2" y="-20"/>
+ <delta pt="23" x="-2" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="-10" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="5" y="0"/>
+ <delta pt="1" x="5" y="19"/>
+ <delta pt="2" x="9" y="19"/>
+ <delta pt="3" x="6" y="19"/>
+ <delta pt="4" x="-15" y="19"/>
+ <delta pt="5" x="-15" y="0"/>
+ <delta pt="6" x="-6" y="0"/>
+ <delta pt="7" x="-14" y="-23"/>
+ <delta pt="8" x="46" y="-23"/>
+ <delta pt="9" x="39" y="0"/>
+ <delta pt="10" x="-69" y="0"/>
+ <delta pt="11" x="-27" y="-86"/>
+ <delta pt="12" x="-7" y="-16"/>
+ <delta pt="13" x="11" y="0"/>
+ <delta pt="14" x="-2" y="-39"/>
+ <delta pt="15" x="-1" y="-22"/>
+ <delta pt="16" x="-1" y="-22"/>
+ <delta pt="17" x="8" y="-39"/>
+ <delta pt="18" x="-41" y="0"/>
+ <delta pt="19" x="-41" y="16"/>
+ <delta pt="20" x="-59" y="16"/>
+ <delta pt="21" x="6" y="16"/>
+ <delta pt="22" x="12" y="16"/>
+ <delta pt="23" x="12" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="17" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="2" y="0"/>
+ <delta pt="1" x="2" y="-9"/>
+ <delta pt="2" x="-4" y="-9"/>
+ <delta pt="3" x="-4" y="-9"/>
+ <delta pt="4" x="-2" y="-9"/>
+ <delta pt="5" x="-2" y="0"/>
+ <delta pt="6" x="2" y="0"/>
+ <delta pt="7" x="-4" y="0"/>
+ <delta pt="8" x="-4" y="0"/>
+ <delta pt="9" x="-4" y="0"/>
+ <delta pt="10" x="-4" y="0"/>
+ <delta pt="11" x="-6" y="8"/>
+ <delta pt="12" x="-10" y="0"/>
+ <delta pt="13" x="-2" y="0"/>
+ <delta pt="14" x="0" y="5"/>
+ <delta pt="15" x="-3" y="-5"/>
+ <delta pt="16" x="5" y="-5"/>
+ <delta pt="17" x="-1" y="5"/>
+ <delta pt="18" x="0" y="0"/>
+ <delta pt="19" x="0" y="-8"/>
+ <delta pt="20" x="4" y="-8"/>
+ <delta pt="21" x="0" y="-8"/>
+ <delta pt="22" x="0" y="-8"/>
+ <delta pt="23" x="0" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="-1.0"/>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="-2" y="0"/>
+ <delta pt="1" x="-2" y="9"/>
+ <delta pt="2" x="4" y="9"/>
+ <delta pt="3" x="4" y="9"/>
+ <delta pt="4" x="2" y="9"/>
+ <delta pt="5" x="2" y="0"/>
+ <delta pt="6" x="-2" y="0"/>
+ <delta pt="7" x="4" y="0"/>
+ <delta pt="8" x="4" y="0"/>
+ <delta pt="9" x="4" y="0"/>
+ <delta pt="10" x="4" y="0"/>
+ <delta pt="11" x="6" y="-8"/>
+ <delta pt="12" x="10" y="0"/>
+ <delta pt="13" x="2" y="0"/>
+ <delta pt="14" x="0" y="-5"/>
+ <delta pt="15" x="3" y="5"/>
+ <delta pt="16" x="-5" y="5"/>
+ <delta pt="17" x="1" y="-5"/>
+ <delta pt="18" x="0" y="0"/>
+ <delta pt="19" x="0" y="8"/>
+ <delta pt="20" x="-4" y="8"/>
+ <delta pt="21" x="0" y="8"/>
+ <delta pt="22" x="0" y="8"/>
+ <delta pt="23" x="0" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="3" y="0"/>
+ <delta pt="1" x="3" y="-15"/>
+ <delta pt="2" x="-6" y="-15"/>
+ <delta pt="3" x="-6" y="-15"/>
+ <delta pt="4" x="-3" y="-15"/>
+ <delta pt="5" x="-3" y="0"/>
+ <delta pt="6" x="3" y="0"/>
+ <delta pt="7" x="-6" y="0"/>
+ <delta pt="8" x="-6" y="0"/>
+ <delta pt="9" x="-6" y="0"/>
+ <delta pt="10" x="-6" y="0"/>
+ <delta pt="11" x="-11" y="13"/>
+ <delta pt="12" x="-17" y="0"/>
+ <delta pt="13" x="-3" y="0"/>
+ <delta pt="14" x="-1" y="8"/>
+ <delta pt="15" x="-5" y="-9"/>
+ <delta pt="16" x="8" y="-9"/>
+ <delta pt="17" x="-1" y="8"/>
+ <delta pt="18" x="0" y="0"/>
+ <delta pt="19" x="0" y="-13"/>
+ <delta pt="20" x="6" y="-13"/>
+ <delta pt="21" x="0" y="-13"/>
+ <delta pt="22" x="0" y="-13"/>
+ <delta pt="23" x="0" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="uni0061">
+ <tuple>
+ <coord axis="wght" value="-1.0"/>
+ <delta pt="0" x="11" y="-8"/>
+ <delta pt="1" x="11" y="4"/>
+ <delta pt="2" x="22" y="5"/>
+ <delta pt="3" x="-4" y="-8"/>
+ <delta pt="4" x="6" y="-5"/>
+ <delta pt="5" x="3" y="-11"/>
+ <delta pt="6" x="4" y="-9"/>
+ <delta pt="7" x="4" y="9"/>
+ <delta pt="8" x="0" y="7"/>
+ <delta pt="9" x="-9" y="8"/>
+ <delta pt="10" x="-24" y="3"/>
+ <delta pt="11" x="-18" y="6"/>
+ <delta pt="12" x="-44" y="1"/>
+ <delta pt="13" x="-44" y="-16"/>
+ <delta pt="14" x="-44" y="-22"/>
+ <delta pt="15" x="-36" y="-39"/>
+ <delta pt="16" x="-24" y="-39"/>
+ <delta pt="17" x="-7" y="-39"/>
+ <delta pt="18" x="26" y="-15"/>
+ <delta pt="19" x="26" y="3"/>
+ <delta pt="20" x="17" y="0"/>
+ <delta pt="21" x="3" y="-4"/>
+ <delta pt="22" x="23" y="15"/>
+ <delta pt="23" x="22" y="8"/>
+ <delta pt="24" x="6" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="2" y="0"/>
+ <delta pt="27" x="11" y="-2"/>
+ <delta pt="28" x="30" y="7"/>
+ <delta pt="29" x="30" y="4"/>
+ <delta pt="30" x="30" y="13"/>
+ <delta pt="31" x="14" y="21"/>
+ <delta pt="32" x="3" y="21"/>
+ <delta pt="33" x="-15" y="21"/>
+ <delta pt="34" x="-32" y="5"/>
+ <delta pt="35" x="-34" y="-9"/>
+ <delta pt="36" x="-48" y="-14"/>
+ <delta pt="37" x="-40" y="4"/>
+ <delta pt="38" x="-36" y="14"/>
+ <delta pt="39" x="-24" y="27"/>
+ <delta pt="40" x="-13" y="27"/>
+ <delta pt="41" x="12" y="27"/>
+ <delta pt="42" x="10" y="6"/>
+ <delta pt="43" x="12" y="5"/>
+ <delta pt="44" x="-4" y="-4"/>
+ <delta pt="45" x="-16" y="-4"/>
+ <delta pt="46" x="-20" y="-4"/>
+ <delta pt="47" x="-22" y="7"/>
+ <delta pt="48" x="-22" y="25"/>
+ <delta pt="49" x="-22" y="10"/>
+ <delta pt="50" x="-22" y="-15"/>
+ <delta pt="51" x="-16" y="-30"/>
+ <delta pt="52" x="-9" y="-30"/>
+ <delta pt="53" x="-12" y="-30"/>
+ <delta pt="54" x="-11" y="-35"/>
+ <delta pt="55" x="-5" y="-35"/>
+ <delta pt="56" x="-15" y="-27"/>
+ <delta pt="57" x="-10" y="-3"/>
+ <delta pt="58" x="9" y="-3"/>
+ <delta pt="59" x="14" y="-3"/>
+ <delta pt="60" x="33" y="-1"/>
+ <delta pt="61" x="0" y="0"/>
+ <delta pt="62" x="-3" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ <delta pt="64" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="-21" y="1"/>
+ <delta pt="1" x="-21" y="17"/>
+ <delta pt="2" x="-2" y="28"/>
+ <delta pt="3" x="20" y="23"/>
+ <delta pt="4" x="19" y="20"/>
+ <delta pt="5" x="28" y="21"/>
+ <delta pt="6" x="26" y="23"/>
+ <delta pt="7" x="26" y="15"/>
+ <delta pt="8" x="24" y="12"/>
+ <delta pt="9" x="30" y="17"/>
+ <delta pt="10" x="31" y="15"/>
+ <delta pt="11" x="77" y="31"/>
+ <delta pt="12" x="66" y="36"/>
+ <delta pt="13" x="66" y="18"/>
+ <delta pt="14" x="66" y="21"/>
+ <delta pt="15" x="49" y="19"/>
+ <delta pt="16" x="37" y="19"/>
+ <delta pt="17" x="21" y="19"/>
+ <delta pt="18" x="-2" y="5"/>
+ <delta pt="19" x="-34" y="-18"/>
+ <delta pt="20" x="-6" y="3"/>
+ <delta pt="21" x="-11" y="12"/>
+ <delta pt="22" x="-29" y="-11"/>
+ <delta pt="23" x="-17" y="-2"/>
+ <delta pt="24" x="-13" y="-3"/>
+ <delta pt="25" x="-25" y="-3"/>
+ <delta pt="26" x="-29" y="-3"/>
+ <delta pt="27" x="-21" y="2"/>
+ <delta pt="28" x="-34" y="-14"/>
+ <delta pt="29" x="-34" y="17"/>
+ <delta pt="30" x="-34" y="7"/>
+ <delta pt="31" x="-18" y="7"/>
+ <delta pt="32" x="-16" y="7"/>
+ <delta pt="33" x="-18" y="7"/>
+ <delta pt="34" x="-15" y="9"/>
+ <delta pt="35" x="-21" y="12"/>
+ <delta pt="36" x="19" y="23"/>
+ <delta pt="37" x="45" y="46"/>
+ <delta pt="38" x="52" y="7"/>
+ <delta pt="39" x="26" y="-21"/>
+ <delta pt="40" x="14" y="-21"/>
+ <delta pt="41" x="-5" y="-21"/>
+ <delta pt="42" x="-17" y="-7"/>
+ <delta pt="43" x="-31" y="1"/>
+ <delta pt="44" x="-12" y="16"/>
+ <delta pt="45" x="34" y="16"/>
+ <delta pt="46" x="61" y="16"/>
+ <delta pt="47" x="70" y="4"/>
+ <delta pt="48" x="70" y="-5"/>
+ <delta pt="49" x="70" y="-22"/>
+ <delta pt="50" x="70" y="4"/>
+ <delta pt="51" x="59" y="22"/>
+ <delta pt="52" x="50" y="22"/>
+ <delta pt="53" x="43" y="22"/>
+ <delta pt="54" x="37" y="19"/>
+ <delta pt="55" x="38" y="22"/>
+ <delta pt="56" x="47" y="28"/>
+ <delta pt="57" x="46" y="-6"/>
+ <delta pt="58" x="-2" y="-6"/>
+ <delta pt="59" x="-16" y="-6"/>
+ <delta pt="60" x="-25" y="-13"/>
+ <delta pt="61" x="0" y="0"/>
+ <delta pt="62" x="32" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ <delta pt="64" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="0" y="-3"/>
+ <delta pt="1" x="0" y="-1"/>
+ <delta pt="2" x="0" y="-3"/>
+ <delta pt="3" x="0" y="-3"/>
+ <delta pt="4" x="0" y="-3"/>
+ <delta pt="5" x="0" y="-3"/>
+ <delta pt="6" x="0" y="-3"/>
+ <delta pt="7" x="0" y="4"/>
+ <delta pt="8" x="0" y="4"/>
+ <delta pt="9" x="2" y="5"/>
+ <delta pt="10" x="6" y="7"/>
+ <delta pt="11" x="1" y="5"/>
+ <delta pt="12" x="0" y="-1"/>
+ <delta pt="13" x="0" y="-6"/>
+ <delta pt="14" x="0" y="-6"/>
+ <delta pt="15" x="-1" y="-6"/>
+ <delta pt="16" x="0" y="-6"/>
+ <delta pt="17" x="0" y="-6"/>
+ <delta pt="18" x="0" y="-5"/>
+ <delta pt="19" x="0" y="-4"/>
+ <delta pt="20" x="0" y="-1"/>
+ <delta pt="21" x="0" y="0"/>
+ <delta pt="22" x="0" y="0"/>
+ <delta pt="23" x="0" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="-1"/>
+ <delta pt="28" x="0" y="-2"/>
+ <delta pt="29" x="0" y="7"/>
+ <delta pt="30" x="0" y="6"/>
+ <delta pt="31" x="0" y="7"/>
+ <delta pt="32" x="0" y="7"/>
+ <delta pt="33" x="0" y="7"/>
+ <delta pt="34" x="0" y="7"/>
+ <delta pt="35" x="0" y="7"/>
+ <delta pt="36" x="0" y="0"/>
+ <delta pt="37" x="0" y="0"/>
+ <delta pt="38" x="0" y="0"/>
+ <delta pt="39" x="0" y="0"/>
+ <delta pt="40" x="0" y="0"/>
+ <delta pt="41" x="0" y="0"/>
+ <delta pt="42" x="0" y="0"/>
+ <delta pt="43" x="0" y="0"/>
+ <delta pt="44" x="0" y="0"/>
+ <delta pt="45" x="0" y="0"/>
+ <delta pt="46" x="0" y="0"/>
+ <delta pt="47" x="0" y="0"/>
+ <delta pt="48" x="0" y="0"/>
+ <delta pt="49" x="0" y="-6"/>
+ <delta pt="50" x="0" y="-7"/>
+ <delta pt="51" x="0" y="-8"/>
+ <delta pt="52" x="0" y="-8"/>
+ <delta pt="53" x="1" y="-8"/>
+ <delta pt="54" x="2" y="-5"/>
+ <delta pt="55" x="4" y="-2"/>
+ <delta pt="56" x="0" y="0"/>
+ <delta pt="57" x="0" y="0"/>
+ <delta pt="58" x="0" y="0"/>
+ <delta pt="59" x="0" y="0"/>
+ <delta pt="60" x="0" y="-1"/>
+ <delta pt="61" x="0" y="0"/>
+ <delta pt="62" x="0" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ <delta pt="64" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="-1.0"/>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="0" y="3"/>
+ <delta pt="1" x="0" y="1"/>
+ <delta pt="2" x="0" y="3"/>
+ <delta pt="3" x="0" y="3"/>
+ <delta pt="4" x="0" y="3"/>
+ <delta pt="5" x="0" y="3"/>
+ <delta pt="6" x="0" y="3"/>
+ <delta pt="7" x="0" y="-4"/>
+ <delta pt="8" x="0" y="-4"/>
+ <delta pt="9" x="-2" y="-5"/>
+ <delta pt="10" x="-6" y="-7"/>
+ <delta pt="11" x="-1" y="-5"/>
+ <delta pt="12" x="0" y="1"/>
+ <delta pt="13" x="0" y="6"/>
+ <delta pt="14" x="0" y="6"/>
+ <delta pt="15" x="1" y="6"/>
+ <delta pt="16" x="0" y="6"/>
+ <delta pt="17" x="0" y="6"/>
+ <delta pt="18" x="0" y="5"/>
+ <delta pt="19" x="0" y="4"/>
+ <delta pt="20" x="0" y="1"/>
+ <delta pt="21" x="0" y="0"/>
+ <delta pt="22" x="0" y="0"/>
+ <delta pt="23" x="0" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="1"/>
+ <delta pt="28" x="0" y="2"/>
+ <delta pt="29" x="0" y="-7"/>
+ <delta pt="30" x="0" y="-6"/>
+ <delta pt="31" x="0" y="-7"/>
+ <delta pt="32" x="0" y="-7"/>
+ <delta pt="33" x="0" y="-7"/>
+ <delta pt="34" x="0" y="-7"/>
+ <delta pt="35" x="0" y="-7"/>
+ <delta pt="36" x="0" y="0"/>
+ <delta pt="37" x="0" y="0"/>
+ <delta pt="38" x="0" y="0"/>
+ <delta pt="39" x="0" y="0"/>
+ <delta pt="40" x="0" y="0"/>
+ <delta pt="41" x="0" y="0"/>
+ <delta pt="42" x="0" y="0"/>
+ <delta pt="43" x="0" y="0"/>
+ <delta pt="44" x="0" y="0"/>
+ <delta pt="45" x="0" y="0"/>
+ <delta pt="46" x="0" y="0"/>
+ <delta pt="47" x="0" y="0"/>
+ <delta pt="48" x="0" y="0"/>
+ <delta pt="49" x="0" y="6"/>
+ <delta pt="50" x="0" y="7"/>
+ <delta pt="51" x="0" y="8"/>
+ <delta pt="52" x="0" y="8"/>
+ <delta pt="53" x="-1" y="8"/>
+ <delta pt="54" x="-2" y="5"/>
+ <delta pt="55" x="-4" y="2"/>
+ <delta pt="56" x="0" y="0"/>
+ <delta pt="57" x="0" y="0"/>
+ <delta pt="58" x="0" y="0"/>
+ <delta pt="59" x="0" y="0"/>
+ <delta pt="60" x="0" y="1"/>
+ <delta pt="61" x="0" y="0"/>
+ <delta pt="62" x="0" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ <delta pt="64" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="0" y="-5"/>
+ <delta pt="1" x="0" y="0"/>
+ <delta pt="2" x="3" y="-4"/>
+ <delta pt="3" x="0" y="-4"/>
+ <delta pt="4" x="0" y="-4"/>
+ <delta pt="5" x="0" y="-4"/>
+ <delta pt="6" x="0" y="-4"/>
+ <delta pt="7" x="0" y="8"/>
+ <delta pt="8" x="0" y="8"/>
+ <delta pt="9" x="5" y="9"/>
+ <delta pt="10" x="11" y="13"/>
+ <delta pt="11" x="2" y="10"/>
+ <delta pt="12" x="0" y="0"/>
+ <delta pt="13" x="0" y="-9"/>
+ <delta pt="14" x="0" y="-9"/>
+ <delta pt="15" x="-1" y="-9"/>
+ <delta pt="16" x="0" y="-9"/>
+ <delta pt="17" x="0" y="-9"/>
+ <delta pt="18" x="0" y="-10"/>
+ <delta pt="19" x="0" y="-8"/>
+ <delta pt="20" x="0" y="-2"/>
+ <delta pt="21" x="0" y="1"/>
+ <delta pt="22" x="0" y="0"/>
+ <delta pt="23" x="1" y="-1"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="-1"/>
+ <delta pt="28" x="0" y="-4"/>
+ <delta pt="29" x="0" y="12"/>
+ <delta pt="30" x="0" y="13"/>
+ <delta pt="31" x="0" y="13"/>
+ <delta pt="32" x="0" y="13"/>
+ <delta pt="33" x="0" y="13"/>
+ <delta pt="34" x="0" y="13"/>
+ <delta pt="35" x="0" y="13"/>
+ <delta pt="36" x="0" y="0"/>
+ <delta pt="37" x="0" y="0"/>
+ <delta pt="38" x="0" y="0"/>
+ <delta pt="39" x="0" y="1"/>
+ <delta pt="40" x="0" y="1"/>
+ <delta pt="41" x="0" y="1"/>
+ <delta pt="42" x="0" y="1"/>
+ <delta pt="43" x="0" y="0"/>
+ <delta pt="44" x="0" y="0"/>
+ <delta pt="45" x="0" y="0"/>
+ <delta pt="46" x="0" y="0"/>
+ <delta pt="47" x="0" y="-1"/>
+ <delta pt="48" x="0" y="-1"/>
+ <delta pt="49" x="0" y="-9"/>
+ <delta pt="50" x="0" y="-13"/>
+ <delta pt="51" x="1" y="-14"/>
+ <delta pt="52" x="1" y="-14"/>
+ <delta pt="53" x="2" y="-14"/>
+ <delta pt="54" x="5" y="-11"/>
+ <delta pt="55" x="7" y="-4"/>
+ <delta pt="56" x="0" y="0"/>
+ <delta pt="57" x="0" y="0"/>
+ <delta pt="58" x="0" y="0"/>
+ <delta pt="59" x="0" y="0"/>
+ <delta pt="60" x="1" y="0"/>
+ <delta pt="61" x="0" y="0"/>
+ <delta pt="62" x="0" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ <delta pt="64" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
</gvar>
</ttFont>
diff --git a/Tests/varLib/data/test_results/BuildMain.ttx b/Tests/varLib/data/test_results/BuildMain.ttx
index 7e5d9561..27d02d1d 100644
--- a/Tests/varLib/data/test_results/BuildMain.ttx
+++ b/Tests/varLib/data/test_results/BuildMain.ttx
@@ -615,7 +615,7 @@
<GDEF>
<Version value="0x00010003"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="uni0024" class="1"/>
<ClassDef glyph="uni0024.nostroke" class="1"/>
<ClassDef glyph="uni0041" class="1"/>
@@ -1051,509 +1051,6 @@
<delta pt="3" x="0" y="0"/>
</tuple>
</glyphVariations>
- <glyphVariations glyph="uni0041">
- <tuple>
- <coord axis="wght" value="-1.0"/>
- <delta pt="0" x="7" y="0"/>
- <delta pt="1" x="7" y="-20"/>
- <delta pt="2" x="-6" y="-29"/>
- <delta pt="3" x="-12" y="-29"/>
- <delta pt="4" x="-25" y="-20"/>
- <delta pt="5" x="-25" y="0"/>
- <delta pt="6" x="14" y="0"/>
- <delta pt="7" x="4" y="9"/>
- <delta pt="8" x="-36" y="9"/>
- <delta pt="9" x="-37" y="0"/>
- <delta pt="10" x="24" y="0"/>
- <delta pt="11" x="9" y="58"/>
- <delta pt="12" x="3" y="68"/>
- <delta pt="13" x="-4" y="0"/>
- <delta pt="14" x="3" y="28"/>
- <delta pt="15" x="-4" y="2"/>
- <delta pt="16" x="4" y="2"/>
- <delta pt="17" x="-4" y="28"/>
- <delta pt="18" x="20" y="0"/>
- <delta pt="19" x="20" y="-20"/>
- <delta pt="20" x="14" y="-29"/>
- <delta pt="21" x="8" y="-29"/>
- <delta pt="22" x="-2" y="-20"/>
- <delta pt="23" x="-2" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="-10" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="1.0"/>
- <delta pt="0" x="5" y="0"/>
- <delta pt="1" x="5" y="19"/>
- <delta pt="2" x="9" y="19"/>
- <delta pt="3" x="6" y="19"/>
- <delta pt="4" x="-15" y="19"/>
- <delta pt="5" x="-15" y="0"/>
- <delta pt="6" x="-6" y="0"/>
- <delta pt="7" x="-14" y="-23"/>
- <delta pt="8" x="46" y="-23"/>
- <delta pt="9" x="39" y="0"/>
- <delta pt="10" x="-69" y="0"/>
- <delta pt="11" x="-27" y="-86"/>
- <delta pt="12" x="-7" y="-16"/>
- <delta pt="13" x="11" y="0"/>
- <delta pt="14" x="-2" y="-39"/>
- <delta pt="15" x="-1" y="-22"/>
- <delta pt="16" x="-1" y="-22"/>
- <delta pt="17" x="8" y="-39"/>
- <delta pt="18" x="-41" y="0"/>
- <delta pt="19" x="-41" y="16"/>
- <delta pt="20" x="-59" y="16"/>
- <delta pt="21" x="6" y="16"/>
- <delta pt="22" x="12" y="16"/>
- <delta pt="23" x="12" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="17" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="2" y="0"/>
- <delta pt="1" x="2" y="-9"/>
- <delta pt="2" x="-4" y="-9"/>
- <delta pt="3" x="-4" y="-9"/>
- <delta pt="4" x="-2" y="-9"/>
- <delta pt="5" x="-2" y="0"/>
- <delta pt="6" x="2" y="0"/>
- <delta pt="7" x="-4" y="0"/>
- <delta pt="8" x="-4" y="0"/>
- <delta pt="9" x="-4" y="0"/>
- <delta pt="10" x="-4" y="0"/>
- <delta pt="11" x="-6" y="8"/>
- <delta pt="12" x="-10" y="0"/>
- <delta pt="13" x="-2" y="0"/>
- <delta pt="14" x="0" y="5"/>
- <delta pt="15" x="-3" y="-5"/>
- <delta pt="16" x="5" y="-5"/>
- <delta pt="17" x="-1" y="5"/>
- <delta pt="18" x="0" y="0"/>
- <delta pt="19" x="0" y="-8"/>
- <delta pt="20" x="4" y="-8"/>
- <delta pt="21" x="0" y="-8"/>
- <delta pt="22" x="0" y="-8"/>
- <delta pt="23" x="0" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="-1.0"/>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="-2" y="0"/>
- <delta pt="1" x="-2" y="9"/>
- <delta pt="2" x="4" y="9"/>
- <delta pt="3" x="4" y="9"/>
- <delta pt="4" x="2" y="9"/>
- <delta pt="5" x="2" y="0"/>
- <delta pt="6" x="-2" y="0"/>
- <delta pt="7" x="4" y="0"/>
- <delta pt="8" x="4" y="0"/>
- <delta pt="9" x="4" y="0"/>
- <delta pt="10" x="4" y="0"/>
- <delta pt="11" x="6" y="-8"/>
- <delta pt="12" x="10" y="0"/>
- <delta pt="13" x="2" y="0"/>
- <delta pt="14" x="0" y="-5"/>
- <delta pt="15" x="3" y="5"/>
- <delta pt="16" x="-5" y="5"/>
- <delta pt="17" x="1" y="-5"/>
- <delta pt="18" x="0" y="0"/>
- <delta pt="19" x="0" y="8"/>
- <delta pt="20" x="-4" y="8"/>
- <delta pt="21" x="0" y="8"/>
- <delta pt="22" x="0" y="8"/>
- <delta pt="23" x="0" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="1.0"/>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="3" y="0"/>
- <delta pt="1" x="3" y="-15"/>
- <delta pt="2" x="-6" y="-15"/>
- <delta pt="3" x="-6" y="-15"/>
- <delta pt="4" x="-3" y="-15"/>
- <delta pt="5" x="-3" y="0"/>
- <delta pt="6" x="3" y="0"/>
- <delta pt="7" x="-6" y="0"/>
- <delta pt="8" x="-6" y="0"/>
- <delta pt="9" x="-6" y="0"/>
- <delta pt="10" x="-6" y="0"/>
- <delta pt="11" x="-11" y="13"/>
- <delta pt="12" x="-17" y="0"/>
- <delta pt="13" x="-3" y="0"/>
- <delta pt="14" x="-1" y="8"/>
- <delta pt="15" x="-5" y="-9"/>
- <delta pt="16" x="8" y="-9"/>
- <delta pt="17" x="-1" y="8"/>
- <delta pt="18" x="0" y="0"/>
- <delta pt="19" x="0" y="-13"/>
- <delta pt="20" x="6" y="-13"/>
- <delta pt="21" x="0" y="-13"/>
- <delta pt="22" x="0" y="-13"/>
- <delta pt="23" x="0" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="0"/>
- </tuple>
- </glyphVariations>
- <glyphVariations glyph="uni0061">
- <tuple>
- <coord axis="wght" value="-1.0"/>
- <delta pt="0" x="11" y="-8"/>
- <delta pt="1" x="11" y="4"/>
- <delta pt="2" x="22" y="5"/>
- <delta pt="3" x="-4" y="-8"/>
- <delta pt="4" x="6" y="-5"/>
- <delta pt="5" x="3" y="-11"/>
- <delta pt="6" x="4" y="-9"/>
- <delta pt="7" x="4" y="9"/>
- <delta pt="8" x="0" y="7"/>
- <delta pt="9" x="-9" y="8"/>
- <delta pt="10" x="-24" y="3"/>
- <delta pt="11" x="-18" y="6"/>
- <delta pt="12" x="-44" y="1"/>
- <delta pt="13" x="-44" y="-16"/>
- <delta pt="14" x="-44" y="-22"/>
- <delta pt="15" x="-36" y="-39"/>
- <delta pt="16" x="-24" y="-39"/>
- <delta pt="17" x="-7" y="-39"/>
- <delta pt="18" x="26" y="-15"/>
- <delta pt="19" x="26" y="3"/>
- <delta pt="20" x="17" y="0"/>
- <delta pt="21" x="3" y="-4"/>
- <delta pt="22" x="23" y="15"/>
- <delta pt="23" x="22" y="8"/>
- <delta pt="24" x="6" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="2" y="0"/>
- <delta pt="27" x="11" y="-2"/>
- <delta pt="28" x="30" y="7"/>
- <delta pt="29" x="30" y="4"/>
- <delta pt="30" x="30" y="13"/>
- <delta pt="31" x="14" y="21"/>
- <delta pt="32" x="3" y="21"/>
- <delta pt="33" x="-15" y="21"/>
- <delta pt="34" x="-32" y="5"/>
- <delta pt="35" x="-34" y="-9"/>
- <delta pt="36" x="-48" y="-14"/>
- <delta pt="37" x="-40" y="4"/>
- <delta pt="38" x="-36" y="14"/>
- <delta pt="39" x="-24" y="27"/>
- <delta pt="40" x="-13" y="27"/>
- <delta pt="41" x="12" y="27"/>
- <delta pt="42" x="10" y="6"/>
- <delta pt="43" x="12" y="5"/>
- <delta pt="44" x="-4" y="-4"/>
- <delta pt="45" x="-16" y="-4"/>
- <delta pt="46" x="-20" y="-4"/>
- <delta pt="47" x="-22" y="7"/>
- <delta pt="48" x="-22" y="25"/>
- <delta pt="49" x="-22" y="10"/>
- <delta pt="50" x="-22" y="-15"/>
- <delta pt="51" x="-16" y="-30"/>
- <delta pt="52" x="-9" y="-30"/>
- <delta pt="53" x="-12" y="-30"/>
- <delta pt="54" x="-11" y="-35"/>
- <delta pt="55" x="-5" y="-35"/>
- <delta pt="56" x="-15" y="-27"/>
- <delta pt="57" x="-10" y="-3"/>
- <delta pt="58" x="9" y="-3"/>
- <delta pt="59" x="14" y="-3"/>
- <delta pt="60" x="33" y="-1"/>
- <delta pt="61" x="0" y="0"/>
- <delta pt="62" x="-3" y="0"/>
- <delta pt="63" x="0" y="0"/>
- <delta pt="64" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="1.0"/>
- <delta pt="0" x="-21" y="1"/>
- <delta pt="1" x="-21" y="17"/>
- <delta pt="2" x="-2" y="28"/>
- <delta pt="3" x="20" y="23"/>
- <delta pt="4" x="19" y="20"/>
- <delta pt="5" x="28" y="21"/>
- <delta pt="6" x="26" y="23"/>
- <delta pt="7" x="26" y="15"/>
- <delta pt="8" x="24" y="12"/>
- <delta pt="9" x="30" y="17"/>
- <delta pt="10" x="31" y="15"/>
- <delta pt="11" x="77" y="31"/>
- <delta pt="12" x="66" y="36"/>
- <delta pt="13" x="66" y="18"/>
- <delta pt="14" x="66" y="21"/>
- <delta pt="15" x="49" y="19"/>
- <delta pt="16" x="37" y="19"/>
- <delta pt="17" x="21" y="19"/>
- <delta pt="18" x="-2" y="5"/>
- <delta pt="19" x="-34" y="-18"/>
- <delta pt="20" x="-6" y="3"/>
- <delta pt="21" x="-11" y="12"/>
- <delta pt="22" x="-29" y="-11"/>
- <delta pt="23" x="-17" y="-2"/>
- <delta pt="24" x="-13" y="-3"/>
- <delta pt="25" x="-25" y="-3"/>
- <delta pt="26" x="-29" y="-3"/>
- <delta pt="27" x="-21" y="2"/>
- <delta pt="28" x="-34" y="-14"/>
- <delta pt="29" x="-34" y="17"/>
- <delta pt="30" x="-34" y="7"/>
- <delta pt="31" x="-18" y="7"/>
- <delta pt="32" x="-16" y="7"/>
- <delta pt="33" x="-18" y="7"/>
- <delta pt="34" x="-15" y="9"/>
- <delta pt="35" x="-21" y="12"/>
- <delta pt="36" x="19" y="23"/>
- <delta pt="37" x="45" y="46"/>
- <delta pt="38" x="52" y="7"/>
- <delta pt="39" x="26" y="-21"/>
- <delta pt="40" x="14" y="-21"/>
- <delta pt="41" x="-5" y="-21"/>
- <delta pt="42" x="-17" y="-7"/>
- <delta pt="43" x="-31" y="1"/>
- <delta pt="44" x="-12" y="16"/>
- <delta pt="45" x="34" y="16"/>
- <delta pt="46" x="61" y="16"/>
- <delta pt="47" x="70" y="4"/>
- <delta pt="48" x="70" y="-5"/>
- <delta pt="49" x="70" y="-22"/>
- <delta pt="50" x="70" y="4"/>
- <delta pt="51" x="59" y="22"/>
- <delta pt="52" x="50" y="22"/>
- <delta pt="53" x="43" y="22"/>
- <delta pt="54" x="37" y="19"/>
- <delta pt="55" x="38" y="22"/>
- <delta pt="56" x="47" y="28"/>
- <delta pt="57" x="46" y="-6"/>
- <delta pt="58" x="-2" y="-6"/>
- <delta pt="59" x="-16" y="-6"/>
- <delta pt="60" x="-25" y="-13"/>
- <delta pt="61" x="0" y="0"/>
- <delta pt="62" x="32" y="0"/>
- <delta pt="63" x="0" y="0"/>
- <delta pt="64" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="0" y="-3"/>
- <delta pt="1" x="0" y="-1"/>
- <delta pt="2" x="0" y="-3"/>
- <delta pt="3" x="0" y="-3"/>
- <delta pt="4" x="0" y="-3"/>
- <delta pt="5" x="0" y="-3"/>
- <delta pt="6" x="0" y="-3"/>
- <delta pt="7" x="0" y="4"/>
- <delta pt="8" x="0" y="4"/>
- <delta pt="9" x="2" y="5"/>
- <delta pt="10" x="6" y="7"/>
- <delta pt="11" x="1" y="5"/>
- <delta pt="12" x="0" y="-1"/>
- <delta pt="13" x="0" y="-6"/>
- <delta pt="14" x="0" y="-6"/>
- <delta pt="15" x="-1" y="-6"/>
- <delta pt="16" x="0" y="-6"/>
- <delta pt="17" x="0" y="-6"/>
- <delta pt="18" x="0" y="-5"/>
- <delta pt="19" x="0" y="-4"/>
- <delta pt="20" x="0" y="-1"/>
- <delta pt="21" x="0" y="0"/>
- <delta pt="22" x="0" y="0"/>
- <delta pt="23" x="0" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="-1"/>
- <delta pt="28" x="0" y="-2"/>
- <delta pt="29" x="0" y="7"/>
- <delta pt="30" x="0" y="6"/>
- <delta pt="31" x="0" y="7"/>
- <delta pt="32" x="0" y="7"/>
- <delta pt="33" x="0" y="7"/>
- <delta pt="34" x="0" y="7"/>
- <delta pt="35" x="0" y="7"/>
- <delta pt="36" x="0" y="0"/>
- <delta pt="37" x="0" y="0"/>
- <delta pt="38" x="0" y="0"/>
- <delta pt="39" x="0" y="0"/>
- <delta pt="40" x="0" y="0"/>
- <delta pt="41" x="0" y="0"/>
- <delta pt="42" x="0" y="0"/>
- <delta pt="43" x="0" y="0"/>
- <delta pt="44" x="0" y="0"/>
- <delta pt="45" x="0" y="0"/>
- <delta pt="46" x="0" y="0"/>
- <delta pt="47" x="0" y="0"/>
- <delta pt="48" x="0" y="0"/>
- <delta pt="49" x="0" y="-6"/>
- <delta pt="50" x="0" y="-7"/>
- <delta pt="51" x="0" y="-8"/>
- <delta pt="52" x="0" y="-8"/>
- <delta pt="53" x="1" y="-8"/>
- <delta pt="54" x="2" y="-5"/>
- <delta pt="55" x="4" y="-2"/>
- <delta pt="56" x="0" y="0"/>
- <delta pt="57" x="0" y="0"/>
- <delta pt="58" x="0" y="0"/>
- <delta pt="59" x="0" y="0"/>
- <delta pt="60" x="0" y="-1"/>
- <delta pt="61" x="0" y="0"/>
- <delta pt="62" x="0" y="0"/>
- <delta pt="63" x="0" y="0"/>
- <delta pt="64" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="-1.0"/>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="0" y="3"/>
- <delta pt="1" x="0" y="1"/>
- <delta pt="2" x="0" y="3"/>
- <delta pt="3" x="0" y="3"/>
- <delta pt="4" x="0" y="3"/>
- <delta pt="5" x="0" y="3"/>
- <delta pt="6" x="0" y="3"/>
- <delta pt="7" x="0" y="-4"/>
- <delta pt="8" x="0" y="-4"/>
- <delta pt="9" x="-2" y="-5"/>
- <delta pt="10" x="-6" y="-7"/>
- <delta pt="11" x="-1" y="-5"/>
- <delta pt="12" x="0" y="1"/>
- <delta pt="13" x="0" y="6"/>
- <delta pt="14" x="0" y="6"/>
- <delta pt="15" x="1" y="6"/>
- <delta pt="16" x="0" y="6"/>
- <delta pt="17" x="0" y="6"/>
- <delta pt="18" x="0" y="5"/>
- <delta pt="19" x="0" y="4"/>
- <delta pt="20" x="0" y="1"/>
- <delta pt="21" x="0" y="0"/>
- <delta pt="22" x="0" y="0"/>
- <delta pt="23" x="0" y="0"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="1"/>
- <delta pt="28" x="0" y="2"/>
- <delta pt="29" x="0" y="-7"/>
- <delta pt="30" x="0" y="-6"/>
- <delta pt="31" x="0" y="-7"/>
- <delta pt="32" x="0" y="-7"/>
- <delta pt="33" x="0" y="-7"/>
- <delta pt="34" x="0" y="-7"/>
- <delta pt="35" x="0" y="-7"/>
- <delta pt="36" x="0" y="0"/>
- <delta pt="37" x="0" y="0"/>
- <delta pt="38" x="0" y="0"/>
- <delta pt="39" x="0" y="0"/>
- <delta pt="40" x="0" y="0"/>
- <delta pt="41" x="0" y="0"/>
- <delta pt="42" x="0" y="0"/>
- <delta pt="43" x="0" y="0"/>
- <delta pt="44" x="0" y="0"/>
- <delta pt="45" x="0" y="0"/>
- <delta pt="46" x="0" y="0"/>
- <delta pt="47" x="0" y="0"/>
- <delta pt="48" x="0" y="0"/>
- <delta pt="49" x="0" y="6"/>
- <delta pt="50" x="0" y="7"/>
- <delta pt="51" x="0" y="8"/>
- <delta pt="52" x="0" y="8"/>
- <delta pt="53" x="-1" y="8"/>
- <delta pt="54" x="-2" y="5"/>
- <delta pt="55" x="-4" y="2"/>
- <delta pt="56" x="0" y="0"/>
- <delta pt="57" x="0" y="0"/>
- <delta pt="58" x="0" y="0"/>
- <delta pt="59" x="0" y="0"/>
- <delta pt="60" x="0" y="1"/>
- <delta pt="61" x="0" y="0"/>
- <delta pt="62" x="0" y="0"/>
- <delta pt="63" x="0" y="0"/>
- <delta pt="64" x="0" y="0"/>
- </tuple>
- <tuple>
- <coord axis="wght" value="1.0"/>
- <coord axis="cntr" value="1.0"/>
- <delta pt="0" x="0" y="-5"/>
- <delta pt="1" x="0" y="0"/>
- <delta pt="2" x="3" y="-4"/>
- <delta pt="3" x="0" y="-4"/>
- <delta pt="4" x="0" y="-4"/>
- <delta pt="5" x="0" y="-4"/>
- <delta pt="6" x="0" y="-4"/>
- <delta pt="7" x="0" y="8"/>
- <delta pt="8" x="0" y="8"/>
- <delta pt="9" x="5" y="9"/>
- <delta pt="10" x="11" y="13"/>
- <delta pt="11" x="2" y="10"/>
- <delta pt="12" x="0" y="0"/>
- <delta pt="13" x="0" y="-9"/>
- <delta pt="14" x="0" y="-9"/>
- <delta pt="15" x="-1" y="-9"/>
- <delta pt="16" x="0" y="-9"/>
- <delta pt="17" x="0" y="-9"/>
- <delta pt="18" x="0" y="-10"/>
- <delta pt="19" x="0" y="-8"/>
- <delta pt="20" x="0" y="-2"/>
- <delta pt="21" x="0" y="1"/>
- <delta pt="22" x="0" y="0"/>
- <delta pt="23" x="1" y="-1"/>
- <delta pt="24" x="0" y="0"/>
- <delta pt="25" x="0" y="0"/>
- <delta pt="26" x="0" y="0"/>
- <delta pt="27" x="0" y="-1"/>
- <delta pt="28" x="0" y="-4"/>
- <delta pt="29" x="0" y="12"/>
- <delta pt="30" x="0" y="13"/>
- <delta pt="31" x="0" y="13"/>
- <delta pt="32" x="0" y="13"/>
- <delta pt="33" x="0" y="13"/>
- <delta pt="34" x="0" y="13"/>
- <delta pt="35" x="0" y="13"/>
- <delta pt="36" x="0" y="0"/>
- <delta pt="37" x="0" y="0"/>
- <delta pt="38" x="0" y="0"/>
- <delta pt="39" x="0" y="1"/>
- <delta pt="40" x="0" y="1"/>
- <delta pt="41" x="0" y="1"/>
- <delta pt="42" x="0" y="1"/>
- <delta pt="43" x="0" y="0"/>
- <delta pt="44" x="0" y="0"/>
- <delta pt="45" x="0" y="0"/>
- <delta pt="46" x="0" y="0"/>
- <delta pt="47" x="0" y="-1"/>
- <delta pt="48" x="0" y="-1"/>
- <delta pt="49" x="0" y="-9"/>
- <delta pt="50" x="0" y="-13"/>
- <delta pt="51" x="1" y="-14"/>
- <delta pt="52" x="1" y="-14"/>
- <delta pt="53" x="2" y="-14"/>
- <delta pt="54" x="5" y="-11"/>
- <delta pt="55" x="7" y="-4"/>
- <delta pt="56" x="0" y="0"/>
- <delta pt="57" x="0" y="0"/>
- <delta pt="58" x="0" y="0"/>
- <delta pt="59" x="0" y="0"/>
- <delta pt="60" x="1" y="0"/>
- <delta pt="61" x="0" y="0"/>
- <delta pt="62" x="0" y="0"/>
- <delta pt="63" x="0" y="0"/>
- <delta pt="64" x="0" y="0"/>
- </tuple>
- </glyphVariations>
<glyphVariations glyph="uni0024">
<tuple>
<coord axis="wght" value="-1.0"/>
@@ -2252,6 +1749,509 @@
<delta pt="65" x="0" y="0"/>
</tuple>
</glyphVariations>
+ <glyphVariations glyph="uni0041">
+ <tuple>
+ <coord axis="wght" value="-1.0"/>
+ <delta pt="0" x="7" y="0"/>
+ <delta pt="1" x="7" y="-20"/>
+ <delta pt="2" x="-6" y="-29"/>
+ <delta pt="3" x="-12" y="-29"/>
+ <delta pt="4" x="-25" y="-20"/>
+ <delta pt="5" x="-25" y="0"/>
+ <delta pt="6" x="14" y="0"/>
+ <delta pt="7" x="4" y="9"/>
+ <delta pt="8" x="-36" y="9"/>
+ <delta pt="9" x="-37" y="0"/>
+ <delta pt="10" x="24" y="0"/>
+ <delta pt="11" x="9" y="58"/>
+ <delta pt="12" x="3" y="68"/>
+ <delta pt="13" x="-4" y="0"/>
+ <delta pt="14" x="3" y="28"/>
+ <delta pt="15" x="-4" y="2"/>
+ <delta pt="16" x="4" y="2"/>
+ <delta pt="17" x="-4" y="28"/>
+ <delta pt="18" x="20" y="0"/>
+ <delta pt="19" x="20" y="-20"/>
+ <delta pt="20" x="14" y="-29"/>
+ <delta pt="21" x="8" y="-29"/>
+ <delta pt="22" x="-2" y="-20"/>
+ <delta pt="23" x="-2" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="-10" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="5" y="0"/>
+ <delta pt="1" x="5" y="19"/>
+ <delta pt="2" x="9" y="19"/>
+ <delta pt="3" x="6" y="19"/>
+ <delta pt="4" x="-15" y="19"/>
+ <delta pt="5" x="-15" y="0"/>
+ <delta pt="6" x="-6" y="0"/>
+ <delta pt="7" x="-14" y="-23"/>
+ <delta pt="8" x="46" y="-23"/>
+ <delta pt="9" x="39" y="0"/>
+ <delta pt="10" x="-69" y="0"/>
+ <delta pt="11" x="-27" y="-86"/>
+ <delta pt="12" x="-7" y="-16"/>
+ <delta pt="13" x="11" y="0"/>
+ <delta pt="14" x="-2" y="-39"/>
+ <delta pt="15" x="-1" y="-22"/>
+ <delta pt="16" x="-1" y="-22"/>
+ <delta pt="17" x="8" y="-39"/>
+ <delta pt="18" x="-41" y="0"/>
+ <delta pt="19" x="-41" y="16"/>
+ <delta pt="20" x="-59" y="16"/>
+ <delta pt="21" x="6" y="16"/>
+ <delta pt="22" x="12" y="16"/>
+ <delta pt="23" x="12" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="17" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="2" y="0"/>
+ <delta pt="1" x="2" y="-9"/>
+ <delta pt="2" x="-4" y="-9"/>
+ <delta pt="3" x="-4" y="-9"/>
+ <delta pt="4" x="-2" y="-9"/>
+ <delta pt="5" x="-2" y="0"/>
+ <delta pt="6" x="2" y="0"/>
+ <delta pt="7" x="-4" y="0"/>
+ <delta pt="8" x="-4" y="0"/>
+ <delta pt="9" x="-4" y="0"/>
+ <delta pt="10" x="-4" y="0"/>
+ <delta pt="11" x="-6" y="8"/>
+ <delta pt="12" x="-10" y="0"/>
+ <delta pt="13" x="-2" y="0"/>
+ <delta pt="14" x="0" y="5"/>
+ <delta pt="15" x="-3" y="-5"/>
+ <delta pt="16" x="5" y="-5"/>
+ <delta pt="17" x="-1" y="5"/>
+ <delta pt="18" x="0" y="0"/>
+ <delta pt="19" x="0" y="-8"/>
+ <delta pt="20" x="4" y="-8"/>
+ <delta pt="21" x="0" y="-8"/>
+ <delta pt="22" x="0" y="-8"/>
+ <delta pt="23" x="0" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="-1.0"/>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="-2" y="0"/>
+ <delta pt="1" x="-2" y="9"/>
+ <delta pt="2" x="4" y="9"/>
+ <delta pt="3" x="4" y="9"/>
+ <delta pt="4" x="2" y="9"/>
+ <delta pt="5" x="2" y="0"/>
+ <delta pt="6" x="-2" y="0"/>
+ <delta pt="7" x="4" y="0"/>
+ <delta pt="8" x="4" y="0"/>
+ <delta pt="9" x="4" y="0"/>
+ <delta pt="10" x="4" y="0"/>
+ <delta pt="11" x="6" y="-8"/>
+ <delta pt="12" x="10" y="0"/>
+ <delta pt="13" x="2" y="0"/>
+ <delta pt="14" x="0" y="-5"/>
+ <delta pt="15" x="3" y="5"/>
+ <delta pt="16" x="-5" y="5"/>
+ <delta pt="17" x="1" y="-5"/>
+ <delta pt="18" x="0" y="0"/>
+ <delta pt="19" x="0" y="8"/>
+ <delta pt="20" x="-4" y="8"/>
+ <delta pt="21" x="0" y="8"/>
+ <delta pt="22" x="0" y="8"/>
+ <delta pt="23" x="0" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="3" y="0"/>
+ <delta pt="1" x="3" y="-15"/>
+ <delta pt="2" x="-6" y="-15"/>
+ <delta pt="3" x="-6" y="-15"/>
+ <delta pt="4" x="-3" y="-15"/>
+ <delta pt="5" x="-3" y="0"/>
+ <delta pt="6" x="3" y="0"/>
+ <delta pt="7" x="-6" y="0"/>
+ <delta pt="8" x="-6" y="0"/>
+ <delta pt="9" x="-6" y="0"/>
+ <delta pt="10" x="-6" y="0"/>
+ <delta pt="11" x="-11" y="13"/>
+ <delta pt="12" x="-17" y="0"/>
+ <delta pt="13" x="-3" y="0"/>
+ <delta pt="14" x="-1" y="8"/>
+ <delta pt="15" x="-5" y="-9"/>
+ <delta pt="16" x="8" y="-9"/>
+ <delta pt="17" x="-1" y="8"/>
+ <delta pt="18" x="0" y="0"/>
+ <delta pt="19" x="0" y="-13"/>
+ <delta pt="20" x="6" y="-13"/>
+ <delta pt="21" x="0" y="-13"/>
+ <delta pt="22" x="0" y="-13"/>
+ <delta pt="23" x="0" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="uni0061">
+ <tuple>
+ <coord axis="wght" value="-1.0"/>
+ <delta pt="0" x="11" y="-8"/>
+ <delta pt="1" x="11" y="4"/>
+ <delta pt="2" x="22" y="5"/>
+ <delta pt="3" x="-4" y="-8"/>
+ <delta pt="4" x="6" y="-5"/>
+ <delta pt="5" x="3" y="-11"/>
+ <delta pt="6" x="4" y="-9"/>
+ <delta pt="7" x="4" y="9"/>
+ <delta pt="8" x="0" y="7"/>
+ <delta pt="9" x="-9" y="8"/>
+ <delta pt="10" x="-24" y="3"/>
+ <delta pt="11" x="-18" y="6"/>
+ <delta pt="12" x="-44" y="1"/>
+ <delta pt="13" x="-44" y="-16"/>
+ <delta pt="14" x="-44" y="-22"/>
+ <delta pt="15" x="-36" y="-39"/>
+ <delta pt="16" x="-24" y="-39"/>
+ <delta pt="17" x="-7" y="-39"/>
+ <delta pt="18" x="26" y="-15"/>
+ <delta pt="19" x="26" y="3"/>
+ <delta pt="20" x="17" y="0"/>
+ <delta pt="21" x="3" y="-4"/>
+ <delta pt="22" x="23" y="15"/>
+ <delta pt="23" x="22" y="8"/>
+ <delta pt="24" x="6" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="2" y="0"/>
+ <delta pt="27" x="11" y="-2"/>
+ <delta pt="28" x="30" y="7"/>
+ <delta pt="29" x="30" y="4"/>
+ <delta pt="30" x="30" y="13"/>
+ <delta pt="31" x="14" y="21"/>
+ <delta pt="32" x="3" y="21"/>
+ <delta pt="33" x="-15" y="21"/>
+ <delta pt="34" x="-32" y="5"/>
+ <delta pt="35" x="-34" y="-9"/>
+ <delta pt="36" x="-48" y="-14"/>
+ <delta pt="37" x="-40" y="4"/>
+ <delta pt="38" x="-36" y="14"/>
+ <delta pt="39" x="-24" y="27"/>
+ <delta pt="40" x="-13" y="27"/>
+ <delta pt="41" x="12" y="27"/>
+ <delta pt="42" x="10" y="6"/>
+ <delta pt="43" x="12" y="5"/>
+ <delta pt="44" x="-4" y="-4"/>
+ <delta pt="45" x="-16" y="-4"/>
+ <delta pt="46" x="-20" y="-4"/>
+ <delta pt="47" x="-22" y="7"/>
+ <delta pt="48" x="-22" y="25"/>
+ <delta pt="49" x="-22" y="10"/>
+ <delta pt="50" x="-22" y="-15"/>
+ <delta pt="51" x="-16" y="-30"/>
+ <delta pt="52" x="-9" y="-30"/>
+ <delta pt="53" x="-12" y="-30"/>
+ <delta pt="54" x="-11" y="-35"/>
+ <delta pt="55" x="-5" y="-35"/>
+ <delta pt="56" x="-15" y="-27"/>
+ <delta pt="57" x="-10" y="-3"/>
+ <delta pt="58" x="9" y="-3"/>
+ <delta pt="59" x="14" y="-3"/>
+ <delta pt="60" x="33" y="-1"/>
+ <delta pt="61" x="0" y="0"/>
+ <delta pt="62" x="-3" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ <delta pt="64" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="-21" y="1"/>
+ <delta pt="1" x="-21" y="17"/>
+ <delta pt="2" x="-2" y="28"/>
+ <delta pt="3" x="20" y="23"/>
+ <delta pt="4" x="19" y="20"/>
+ <delta pt="5" x="28" y="21"/>
+ <delta pt="6" x="26" y="23"/>
+ <delta pt="7" x="26" y="15"/>
+ <delta pt="8" x="24" y="12"/>
+ <delta pt="9" x="30" y="17"/>
+ <delta pt="10" x="31" y="15"/>
+ <delta pt="11" x="77" y="31"/>
+ <delta pt="12" x="66" y="36"/>
+ <delta pt="13" x="66" y="18"/>
+ <delta pt="14" x="66" y="21"/>
+ <delta pt="15" x="49" y="19"/>
+ <delta pt="16" x="37" y="19"/>
+ <delta pt="17" x="21" y="19"/>
+ <delta pt="18" x="-2" y="5"/>
+ <delta pt="19" x="-34" y="-18"/>
+ <delta pt="20" x="-6" y="3"/>
+ <delta pt="21" x="-11" y="12"/>
+ <delta pt="22" x="-29" y="-11"/>
+ <delta pt="23" x="-17" y="-2"/>
+ <delta pt="24" x="-13" y="-3"/>
+ <delta pt="25" x="-25" y="-3"/>
+ <delta pt="26" x="-29" y="-3"/>
+ <delta pt="27" x="-21" y="2"/>
+ <delta pt="28" x="-34" y="-14"/>
+ <delta pt="29" x="-34" y="17"/>
+ <delta pt="30" x="-34" y="7"/>
+ <delta pt="31" x="-18" y="7"/>
+ <delta pt="32" x="-16" y="7"/>
+ <delta pt="33" x="-18" y="7"/>
+ <delta pt="34" x="-15" y="9"/>
+ <delta pt="35" x="-21" y="12"/>
+ <delta pt="36" x="19" y="23"/>
+ <delta pt="37" x="45" y="46"/>
+ <delta pt="38" x="52" y="7"/>
+ <delta pt="39" x="26" y="-21"/>
+ <delta pt="40" x="14" y="-21"/>
+ <delta pt="41" x="-5" y="-21"/>
+ <delta pt="42" x="-17" y="-7"/>
+ <delta pt="43" x="-31" y="1"/>
+ <delta pt="44" x="-12" y="16"/>
+ <delta pt="45" x="34" y="16"/>
+ <delta pt="46" x="61" y="16"/>
+ <delta pt="47" x="70" y="4"/>
+ <delta pt="48" x="70" y="-5"/>
+ <delta pt="49" x="70" y="-22"/>
+ <delta pt="50" x="70" y="4"/>
+ <delta pt="51" x="59" y="22"/>
+ <delta pt="52" x="50" y="22"/>
+ <delta pt="53" x="43" y="22"/>
+ <delta pt="54" x="37" y="19"/>
+ <delta pt="55" x="38" y="22"/>
+ <delta pt="56" x="47" y="28"/>
+ <delta pt="57" x="46" y="-6"/>
+ <delta pt="58" x="-2" y="-6"/>
+ <delta pt="59" x="-16" y="-6"/>
+ <delta pt="60" x="-25" y="-13"/>
+ <delta pt="61" x="0" y="0"/>
+ <delta pt="62" x="32" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ <delta pt="64" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="0" y="-3"/>
+ <delta pt="1" x="0" y="-1"/>
+ <delta pt="2" x="0" y="-3"/>
+ <delta pt="3" x="0" y="-3"/>
+ <delta pt="4" x="0" y="-3"/>
+ <delta pt="5" x="0" y="-3"/>
+ <delta pt="6" x="0" y="-3"/>
+ <delta pt="7" x="0" y="4"/>
+ <delta pt="8" x="0" y="4"/>
+ <delta pt="9" x="2" y="5"/>
+ <delta pt="10" x="6" y="7"/>
+ <delta pt="11" x="1" y="5"/>
+ <delta pt="12" x="0" y="-1"/>
+ <delta pt="13" x="0" y="-6"/>
+ <delta pt="14" x="0" y="-6"/>
+ <delta pt="15" x="-1" y="-6"/>
+ <delta pt="16" x="0" y="-6"/>
+ <delta pt="17" x="0" y="-6"/>
+ <delta pt="18" x="0" y="-5"/>
+ <delta pt="19" x="0" y="-4"/>
+ <delta pt="20" x="0" y="-1"/>
+ <delta pt="21" x="0" y="0"/>
+ <delta pt="22" x="0" y="0"/>
+ <delta pt="23" x="0" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="-1"/>
+ <delta pt="28" x="0" y="-2"/>
+ <delta pt="29" x="0" y="7"/>
+ <delta pt="30" x="0" y="6"/>
+ <delta pt="31" x="0" y="7"/>
+ <delta pt="32" x="0" y="7"/>
+ <delta pt="33" x="0" y="7"/>
+ <delta pt="34" x="0" y="7"/>
+ <delta pt="35" x="0" y="7"/>
+ <delta pt="36" x="0" y="0"/>
+ <delta pt="37" x="0" y="0"/>
+ <delta pt="38" x="0" y="0"/>
+ <delta pt="39" x="0" y="0"/>
+ <delta pt="40" x="0" y="0"/>
+ <delta pt="41" x="0" y="0"/>
+ <delta pt="42" x="0" y="0"/>
+ <delta pt="43" x="0" y="0"/>
+ <delta pt="44" x="0" y="0"/>
+ <delta pt="45" x="0" y="0"/>
+ <delta pt="46" x="0" y="0"/>
+ <delta pt="47" x="0" y="0"/>
+ <delta pt="48" x="0" y="0"/>
+ <delta pt="49" x="0" y="-6"/>
+ <delta pt="50" x="0" y="-7"/>
+ <delta pt="51" x="0" y="-8"/>
+ <delta pt="52" x="0" y="-8"/>
+ <delta pt="53" x="1" y="-8"/>
+ <delta pt="54" x="2" y="-5"/>
+ <delta pt="55" x="4" y="-2"/>
+ <delta pt="56" x="0" y="0"/>
+ <delta pt="57" x="0" y="0"/>
+ <delta pt="58" x="0" y="0"/>
+ <delta pt="59" x="0" y="0"/>
+ <delta pt="60" x="0" y="-1"/>
+ <delta pt="61" x="0" y="0"/>
+ <delta pt="62" x="0" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ <delta pt="64" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="-1.0"/>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="0" y="3"/>
+ <delta pt="1" x="0" y="1"/>
+ <delta pt="2" x="0" y="3"/>
+ <delta pt="3" x="0" y="3"/>
+ <delta pt="4" x="0" y="3"/>
+ <delta pt="5" x="0" y="3"/>
+ <delta pt="6" x="0" y="3"/>
+ <delta pt="7" x="0" y="-4"/>
+ <delta pt="8" x="0" y="-4"/>
+ <delta pt="9" x="-2" y="-5"/>
+ <delta pt="10" x="-6" y="-7"/>
+ <delta pt="11" x="-1" y="-5"/>
+ <delta pt="12" x="0" y="1"/>
+ <delta pt="13" x="0" y="6"/>
+ <delta pt="14" x="0" y="6"/>
+ <delta pt="15" x="1" y="6"/>
+ <delta pt="16" x="0" y="6"/>
+ <delta pt="17" x="0" y="6"/>
+ <delta pt="18" x="0" y="5"/>
+ <delta pt="19" x="0" y="4"/>
+ <delta pt="20" x="0" y="1"/>
+ <delta pt="21" x="0" y="0"/>
+ <delta pt="22" x="0" y="0"/>
+ <delta pt="23" x="0" y="0"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="1"/>
+ <delta pt="28" x="0" y="2"/>
+ <delta pt="29" x="0" y="-7"/>
+ <delta pt="30" x="0" y="-6"/>
+ <delta pt="31" x="0" y="-7"/>
+ <delta pt="32" x="0" y="-7"/>
+ <delta pt="33" x="0" y="-7"/>
+ <delta pt="34" x="0" y="-7"/>
+ <delta pt="35" x="0" y="-7"/>
+ <delta pt="36" x="0" y="0"/>
+ <delta pt="37" x="0" y="0"/>
+ <delta pt="38" x="0" y="0"/>
+ <delta pt="39" x="0" y="0"/>
+ <delta pt="40" x="0" y="0"/>
+ <delta pt="41" x="0" y="0"/>
+ <delta pt="42" x="0" y="0"/>
+ <delta pt="43" x="0" y="0"/>
+ <delta pt="44" x="0" y="0"/>
+ <delta pt="45" x="0" y="0"/>
+ <delta pt="46" x="0" y="0"/>
+ <delta pt="47" x="0" y="0"/>
+ <delta pt="48" x="0" y="0"/>
+ <delta pt="49" x="0" y="6"/>
+ <delta pt="50" x="0" y="7"/>
+ <delta pt="51" x="0" y="8"/>
+ <delta pt="52" x="0" y="8"/>
+ <delta pt="53" x="-1" y="8"/>
+ <delta pt="54" x="-2" y="5"/>
+ <delta pt="55" x="-4" y="2"/>
+ <delta pt="56" x="0" y="0"/>
+ <delta pt="57" x="0" y="0"/>
+ <delta pt="58" x="0" y="0"/>
+ <delta pt="59" x="0" y="0"/>
+ <delta pt="60" x="0" y="1"/>
+ <delta pt="61" x="0" y="0"/>
+ <delta pt="62" x="0" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ <delta pt="64" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <coord axis="cntr" value="1.0"/>
+ <delta pt="0" x="0" y="-5"/>
+ <delta pt="1" x="0" y="0"/>
+ <delta pt="2" x="3" y="-4"/>
+ <delta pt="3" x="0" y="-4"/>
+ <delta pt="4" x="0" y="-4"/>
+ <delta pt="5" x="0" y="-4"/>
+ <delta pt="6" x="0" y="-4"/>
+ <delta pt="7" x="0" y="8"/>
+ <delta pt="8" x="0" y="8"/>
+ <delta pt="9" x="5" y="9"/>
+ <delta pt="10" x="11" y="13"/>
+ <delta pt="11" x="2" y="10"/>
+ <delta pt="12" x="0" y="0"/>
+ <delta pt="13" x="0" y="-9"/>
+ <delta pt="14" x="0" y="-9"/>
+ <delta pt="15" x="-1" y="-9"/>
+ <delta pt="16" x="0" y="-9"/>
+ <delta pt="17" x="0" y="-9"/>
+ <delta pt="18" x="0" y="-10"/>
+ <delta pt="19" x="0" y="-8"/>
+ <delta pt="20" x="0" y="-2"/>
+ <delta pt="21" x="0" y="1"/>
+ <delta pt="22" x="0" y="0"/>
+ <delta pt="23" x="1" y="-1"/>
+ <delta pt="24" x="0" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="0" y="-1"/>
+ <delta pt="28" x="0" y="-4"/>
+ <delta pt="29" x="0" y="12"/>
+ <delta pt="30" x="0" y="13"/>
+ <delta pt="31" x="0" y="13"/>
+ <delta pt="32" x="0" y="13"/>
+ <delta pt="33" x="0" y="13"/>
+ <delta pt="34" x="0" y="13"/>
+ <delta pt="35" x="0" y="13"/>
+ <delta pt="36" x="0" y="0"/>
+ <delta pt="37" x="0" y="0"/>
+ <delta pt="38" x="0" y="0"/>
+ <delta pt="39" x="0" y="1"/>
+ <delta pt="40" x="0" y="1"/>
+ <delta pt="41" x="0" y="1"/>
+ <delta pt="42" x="0" y="1"/>
+ <delta pt="43" x="0" y="0"/>
+ <delta pt="44" x="0" y="0"/>
+ <delta pt="45" x="0" y="0"/>
+ <delta pt="46" x="0" y="0"/>
+ <delta pt="47" x="0" y="-1"/>
+ <delta pt="48" x="0" y="-1"/>
+ <delta pt="49" x="0" y="-9"/>
+ <delta pt="50" x="0" y="-13"/>
+ <delta pt="51" x="1" y="-14"/>
+ <delta pt="52" x="1" y="-14"/>
+ <delta pt="53" x="2" y="-14"/>
+ <delta pt="54" x="5" y="-11"/>
+ <delta pt="55" x="7" y="-4"/>
+ <delta pt="56" x="0" y="0"/>
+ <delta pt="57" x="0" y="0"/>
+ <delta pt="58" x="0" y="0"/>
+ <delta pt="59" x="0" y="0"/>
+ <delta pt="60" x="1" y="0"/>
+ <delta pt="61" x="0" y="0"/>
+ <delta pt="62" x="0" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ <delta pt="64" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
</gvar>
<ltag>
diff --git a/Tests/varLib/data/test_results/BuildTestCFF2.ttx b/Tests/varLib/data/test_results/BuildTestCFF2.ttx
index 29044525..c4b93778 100644
--- a/Tests/varLib/data/test_results/BuildTestCFF2.ttx
+++ b/Tests/varLib/data/test_results/BuildTestCFF2.ttx
@@ -27,31 +27,31 @@
<!-- Regular -->
<!-- PostScript: TestCFF2Roman-Regular -->
- <NamedInstance flags="0x0" postscriptNameID="262" subfamilyNameID="261">
+ <NamedInstance flags="0x0" postscriptNameID="261" subfamilyNameID="2">
<coord axis="wght" value="400.0"/>
</NamedInstance>
<!-- Medium -->
<!-- PostScript: TestCFF2Roman-Medium -->
- <NamedInstance flags="0x0" postscriptNameID="264" subfamilyNameID="263">
+ <NamedInstance flags="0x0" postscriptNameID="263" subfamilyNameID="262">
<coord axis="wght" value="500.0"/>
</NamedInstance>
<!-- Semibold -->
<!-- PostScript: TestCFF2Roman-Semibold -->
- <NamedInstance flags="0x0" postscriptNameID="266" subfamilyNameID="265">
+ <NamedInstance flags="0x0" postscriptNameID="265" subfamilyNameID="264">
<coord axis="wght" value="600.0"/>
</NamedInstance>
<!-- Bold -->
<!-- PostScript: TestCFF2Roman-Bold -->
- <NamedInstance flags="0x0" postscriptNameID="268" subfamilyNameID="267">
+ <NamedInstance flags="0x0" postscriptNameID="267" subfamilyNameID="266">
<coord axis="wght" value="700.0"/>
</NamedInstance>
<!-- Black -->
<!-- PostScript: TestCFF2Roman-Black -->
- <NamedInstance flags="0x0" postscriptNameID="270" subfamilyNameID="269">
+ <NamedInstance flags="0x0" postscriptNameID="269" subfamilyNameID="268">
<coord axis="wght" value="900.0"/>
</NamedInstance>
</fvar>
@@ -91,6 +91,8 @@
<StdVW>
<blend value="85 -51 87"/>
</StdVW>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
</Private>
</FontDict>
</FDArray>
diff --git a/Tests/varLib/data/test_results/FeatureVars.ttx b/Tests/varLib/data/test_results/FeatureVars.ttx
index 93ad795f..ca24f41c 100644
--- a/Tests/varLib/data/test_results/FeatureVars.ttx
+++ b/Tests/varLib/data/test_results/FeatureVars.ttx
@@ -43,7 +43,7 @@
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
- <FeatureTag value="rvrn"/>
+ <FeatureTag value="rclt"/>
<Feature>
<!-- LookupCount=0 -->
</Feature>
@@ -55,7 +55,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uni0024" out="uni0024.nostroke"/>
</SingleSubst>
</Lookup>
@@ -63,7 +63,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uni0041" out="uni0061"/>
</SingleSubst>
</Lookup>
@@ -71,7 +71,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="uni0061" out="uni0041"/>
</SingleSubst>
</Lookup>
diff --git a/Tests/varLib/data/test_results/FeatureVarsCustomTag.ttx b/Tests/varLib/data/test_results/FeatureVarsCustomTag.ttx
new file mode 100644
index 00000000..3f9e1e08
--- /dev/null
+++ b/Tests/varLib/data/test_results/FeatureVarsCustomTag.ttx
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.29">
+
+ <fvar>
+
+ <!-- Weight -->
+ <Axis>
+ <AxisTag>wght</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>368.0</DefaultValue>
+ <MaxValue>1000.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+
+ <!-- Contrast -->
+ <Axis>
+ <AxisTag>cntr</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>0.0</DefaultValue>
+ <MaxValue>100.0</MaxValue>
+ <AxisNameID>257</AxisNameID>
+ </Axis>
+ </fvar>
+
+ <GSUB>
+ <Version value="0x00010001"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="calt"/>
+ <Feature>
+ <!-- LookupCount=0 -->
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=3 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni0024" out="uni0024.nostroke"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni0041" out="uni0061"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni0061" out="uni0041"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ <FeatureVariations>
+ <Version value="0x00010000"/>
+ <!-- FeatureVariationCount=4 -->
+ <FeatureVariationRecord index="0">
+ <ConditionSet>
+ <!-- ConditionCount=2 -->
+ <ConditionTable index="0" Format="1">
+ <AxisIndex value="1"/>
+ <FilterRangeMinValue value="0.75"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ <ConditionTable index="1" Format="1">
+ <AxisIndex value="0"/>
+ <FilterRangeMinValue value="0.20886"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=1 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=2 -->
+ <LookupListIndex index="0" value="0"/>
+ <LookupListIndex index="1" value="1"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ <FeatureVariationRecord index="1">
+ <ConditionSet>
+ <!-- ConditionCount=2 -->
+ <ConditionTable index="0" Format="1">
+ <AxisIndex value="1"/>
+ <FilterRangeMinValue value="0.0"/>
+ <FilterRangeMaxValue value="0.25"/>
+ </ConditionTable>
+ <ConditionTable index="1" Format="1">
+ <AxisIndex value="0"/>
+ <FilterRangeMinValue value="-1.0"/>
+ <FilterRangeMaxValue value="-0.45654"/>
+ </ConditionTable>
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=1 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="2"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ <FeatureVariationRecord index="2">
+ <ConditionSet>
+ <!-- ConditionCount=1 -->
+ <ConditionTable index="0" Format="1">
+ <AxisIndex value="1"/>
+ <FilterRangeMinValue value="0.75"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=1 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ <FeatureVariationRecord index="3">
+ <ConditionSet>
+ <!-- ConditionCount=1 -->
+ <ConditionTable index="0" Format="1">
+ <AxisIndex value="0"/>
+ <FilterRangeMinValue value="0.20886"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=1 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ </FeatureVariations>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/FeatureVarsWholeRange.ttx b/Tests/varLib/data/test_results/FeatureVarsWholeRange.ttx
new file mode 100644
index 00000000..8ae64da4
--- /dev/null
+++ b/Tests/varLib/data/test_results/FeatureVarsWholeRange.ttx
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.9">
+
+ <fvar>
+
+ <!-- Weight -->
+ <Axis>
+ <AxisTag>wght</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>368.0</DefaultValue>
+ <MaxValue>1000.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+ </fvar>
+
+ <GSUB>
+ <Version value="0x00010001"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="rclt"/>
+ <Feature>
+ <!-- LookupCount=0 -->
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni0024" out="uni0024.nostroke"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ <FeatureVariations>
+ <Version value="0x00010000"/>
+ <!-- FeatureVariationCount=1 -->
+ <FeatureVariationRecord index="0">
+ <ConditionSet>
+ <!-- ConditionCount=0 -->
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=1 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ </FeatureVariations>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/FeatureVars_rclt.ttx b/Tests/varLib/data/test_results/FeatureVars_rclt.ttx
new file mode 100644
index 00000000..b889f3a5
--- /dev/null
+++ b/Tests/varLib/data/test_results/FeatureVars_rclt.ttx
@@ -0,0 +1,249 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.29">
+
+ <fvar>
+
+ <!-- Weight -->
+ <Axis>
+ <AxisTag>wght</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>368.0</DefaultValue>
+ <MaxValue>1000.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+
+ <!-- Contrast -->
+ <Axis>
+ <AxisTag>cntr</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>0.0</DefaultValue>
+ <MaxValue>100.0</MaxValue>
+ <AxisNameID>257</AxisNameID>
+ </Axis>
+ </fvar>
+
+ <GSUB>
+ <Version value="0x00010001"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="latn"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="1"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=1 -->
+ <LangSysRecord index="0">
+ <LangSysTag value="NLD "/>
+ <LangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </LangSys>
+ </LangSysRecord>
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=2 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="rclt"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="rclt"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=5 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni0041" out="uni0061"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni0041" out="uni0061"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni0024" out="uni0024.nostroke"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="3">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni0041" out="uni0061"/>
+ </SingleSubst>
+ </Lookup>
+ <Lookup index="4">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="uni0061" out="uni0041"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ <FeatureVariations>
+ <Version value="0x00010000"/>
+ <!-- FeatureVariationCount=4 -->
+ <FeatureVariationRecord index="0">
+ <ConditionSet>
+ <!-- ConditionCount=2 -->
+ <ConditionTable index="0" Format="1">
+ <AxisIndex value="1"/>
+ <FilterRangeMinValue value="0.75"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ <ConditionTable index="1" Format="1">
+ <AxisIndex value="0"/>
+ <FilterRangeMinValue value="0.20886"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=2 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=3 -->
+ <LookupListIndex index="0" value="0"/>
+ <LookupListIndex index="1" value="2"/>
+ <LookupListIndex index="2" value="3"/>
+ </Feature>
+ </SubstitutionRecord>
+ <SubstitutionRecord index="1">
+ <FeatureIndex value="1"/>
+ <Feature>
+ <!-- LookupCount=3 -->
+ <LookupListIndex index="0" value="1"/>
+ <LookupListIndex index="1" value="2"/>
+ <LookupListIndex index="2" value="3"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ <FeatureVariationRecord index="1">
+ <ConditionSet>
+ <!-- ConditionCount=2 -->
+ <ConditionTable index="0" Format="1">
+ <AxisIndex value="1"/>
+ <FilterRangeMinValue value="0.0"/>
+ <FilterRangeMaxValue value="0.25"/>
+ </ConditionTable>
+ <ConditionTable index="1" Format="1">
+ <AxisIndex value="0"/>
+ <FilterRangeMinValue value="-1.0"/>
+ <FilterRangeMaxValue value="-0.45654"/>
+ </ConditionTable>
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=2 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=2 -->
+ <LookupListIndex index="0" value="0"/>
+ <LookupListIndex index="1" value="4"/>
+ </Feature>
+ </SubstitutionRecord>
+ <SubstitutionRecord index="1">
+ <FeatureIndex value="1"/>
+ <Feature>
+ <!-- LookupCount=2 -->
+ <LookupListIndex index="0" value="1"/>
+ <LookupListIndex index="1" value="4"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ <FeatureVariationRecord index="2">
+ <ConditionSet>
+ <!-- ConditionCount=1 -->
+ <ConditionTable index="0" Format="1">
+ <AxisIndex value="1"/>
+ <FilterRangeMinValue value="0.75"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=2 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=2 -->
+ <LookupListIndex index="0" value="0"/>
+ <LookupListIndex index="1" value="3"/>
+ </Feature>
+ </SubstitutionRecord>
+ <SubstitutionRecord index="1">
+ <FeatureIndex value="1"/>
+ <Feature>
+ <!-- LookupCount=2 -->
+ <LookupListIndex index="0" value="1"/>
+ <LookupListIndex index="1" value="3"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ <FeatureVariationRecord index="3">
+ <ConditionSet>
+ <!-- ConditionCount=1 -->
+ <ConditionTable index="0" Format="1">
+ <AxisIndex value="0"/>
+ <FilterRangeMinValue value="0.20886"/>
+ <FilterRangeMaxValue value="1.0"/>
+ </ConditionTable>
+ </ConditionSet>
+ <FeatureTableSubstitution>
+ <Version value="0x00010000"/>
+ <!-- SubstitutionCount=2 -->
+ <SubstitutionRecord index="0">
+ <FeatureIndex value="0"/>
+ <Feature>
+ <!-- LookupCount=2 -->
+ <LookupListIndex index="0" value="0"/>
+ <LookupListIndex index="1" value="2"/>
+ </Feature>
+ </SubstitutionRecord>
+ <SubstitutionRecord index="1">
+ <FeatureIndex value="1"/>
+ <Feature>
+ <!-- LookupCount=2 -->
+ <LookupListIndex index="0" value="1"/>
+ <LookupListIndex index="1" value="2"/>
+ </Feature>
+ </SubstitutionRecord>
+ </FeatureTableSubstitution>
+ </FeatureVariationRecord>
+ </FeatureVariations>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/InterpolateLayout.ttx b/Tests/varLib/data/test_results/InterpolateLayout.ttx
index b1ea1e99..81e50fbf 100644
--- a/Tests/varLib/data/test_results/InterpolateLayout.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayout.ttx
@@ -93,7 +93,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="A" out="A.sc"/>
</SingleSubst>
</Lookup>
@@ -101,7 +101,7 @@
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <SingleSubst index="0" Format="1">
+ <SingleSubst index="0">
<Substitution in="a" out="a.alt"/>
</SingleSubst>
</Lookup>
@@ -109,7 +109,7 @@
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <MultipleSubst index="0" Format="1">
+ <MultipleSubst index="0">
<Substitution in="ampersand" out="a,n,d"/>
</MultipleSubst>
</Lookup>
@@ -117,7 +117,7 @@
<LookupType value="3"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <AlternateSubst index="0" Format="1">
+ <AlternateSubst index="0">
<AlternateSet glyph="a">
<Alternate glyph="a.alt"/>
<Alternate glyph="A.sc"/>
@@ -128,7 +128,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="f">
<Ligature components="t" glyph="f_t"/>
</LigatureSet>
@@ -141,11 +141,11 @@
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=0 -->
<!-- InputGlyphCount=1 -->
- <InputCoverage index="0" Format="1">
+ <InputCoverage index="0">
<Glyph value="a"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
- <LookAheadCoverage index="0" Format="1">
+ <LookAheadCoverage index="0">
<Glyph value="t"/>
</LookAheadCoverage>
<!-- SubstCount=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff.ttx
index 74e9cc5e..4180a337 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat value="5"/>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff2.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff2.ttx
index 2e21b268..44a7558a 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff2.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_diff2.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="a"/>
</Coverage>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_same.ttx
index a61e75fb..83407c11 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_same.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_1_same.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SinglePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat value="5"/>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff.ttx
index 4f94c37b..0aeb4973 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff.ttx
@@ -34,14 +34,14 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="a" class="1"/>
</ClassDef2>
<!-- Class1Count=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff2.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff2.ttx
index 811ed580..f00c4c3a 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff2.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff2.ttx
@@ -34,16 +34,16 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="a"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="a" class="1"/>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="a" class="1"/>
</ClassDef2>
<!-- Class1Count=2 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_same.ttx
index 98725336..3656964b 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_same.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_same.ttx
@@ -34,14 +34,14 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="2">
+ <ClassDef1>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="a" class="1"/>
</ClassDef2>
<!-- Class1Count=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff.ttx
index 113bd0b1..f85985bb 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff2.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff2.ttx
index efc5ee51..b085109f 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff2.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_diff2.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="a"/>
</Coverage>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_same.ttx
index 014c1ece..2a2a546e 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_same.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_spec_same.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_diff.ttx
index 65d77f9d..993e0a67 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_diff.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_diff.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<CursivePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="a"/>
</Coverage>
<!-- EntryExitCount=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_same.ttx
index b7c8a258..1d5ebcd7 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_same.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_3_same.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<CursivePos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="a"/>
</Coverage>
<!-- EntryExitCount=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_diff.ttx
index 72a8ccf1..7c50f96f 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_diff.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_diff.ttx
@@ -34,10 +34,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="uni0303"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="a"/>
</BaseCoverage>
<!-- ClassCount=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_same.ttx
index 9b41519b..ab96180f 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_same.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_4_same.ttx
@@ -34,10 +34,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="uni0303"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="a"/>
</BaseCoverage>
<!-- ClassCount=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_diff.ttx
index 28480e75..28b5f91e 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_diff.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_diff.ttx
@@ -34,10 +34,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkLigPos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="uni0330"/>
</MarkCoverage>
- <LigatureCoverage Format="1">
+ <LigatureCoverage>
<Glyph value="f_t"/>
</LigatureCoverage>
<!-- ClassCount=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_same.ttx
index 4830f9a3..0df08c06 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_same.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_5_same.ttx
@@ -34,10 +34,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkLigPos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="uni0330"/>
</MarkCoverage>
- <LigatureCoverage Format="1">
+ <LigatureCoverage>
<Glyph value="f_t"/>
</LigatureCoverage>
<!-- ClassCount=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_diff.ttx
index 38d6437e..667d4f12 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_diff.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_diff.ttx
@@ -34,10 +34,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkMarkPos index="0" Format="1">
- <Mark1Coverage Format="1">
+ <Mark1Coverage>
<Glyph value="uni0303"/>
</Mark1Coverage>
- <Mark2Coverage Format="1">
+ <Mark2Coverage>
<Glyph value="uni0308"/>
</Mark2Coverage>
<!-- ClassCount=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_same.ttx
index 05e4b514..34d0bff1 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_same.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_6_same.ttx
@@ -34,10 +34,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkMarkPos index="0" Format="1">
- <Mark1Coverage Format="1">
+ <Mark1Coverage>
<Glyph value="uni0303"/>
</Mark1Coverage>
- <Mark2Coverage Format="1">
+ <Mark2Coverage>
<Glyph value="uni0308"/>
</Mark2Coverage>
<!-- ClassCount=1 -->
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx
index 12f42698..14e12097 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_diff.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
@@ -54,10 +54,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="uni0303"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="a"/>
</BaseCoverage>
<!-- ClassCount=1 -->
@@ -83,32 +83,32 @@
</MarkBasePos>
</Lookup>
<Lookup index="2">
- <LookupType value="8"/>
+ <LookupType value="7"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ChainContextPos index="0" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=3 -->
- <InputCoverage index="0" Format="1">
+ <ContextPos index="0" Format="1">
+ <Coverage>
<Glyph value="A"/>
- </InputCoverage>
- <InputCoverage index="1" Format="1">
- <Glyph value="a"/>
- </InputCoverage>
- <InputCoverage index="2" Format="1">
- <Glyph value="uni0303"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- PosCount=2 -->
- <PosLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="0"/>
- </PosLookupRecord>
- <PosLookupRecord index="1">
- <SequenceIndex value="2"/>
- <LookupListIndex value="1"/>
- </PosLookupRecord>
- </ChainContextPos>
+ </Coverage>
+ <!-- PosRuleSetCount=1 -->
+ <PosRuleSet index="0">
+ <!-- PosRuleCount=1 -->
+ <PosRule index="0">
+ <!-- GlyphCount=3 -->
+ <!-- PosCount=2 -->
+ <Input index="0" value="a"/>
+ <Input index="1" value="uni0303"/>
+ <PosLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="0"/>
+ </PosLookupRecord>
+ <PosLookupRecord index="1">
+ <SequenceIndex value="2"/>
+ <LookupListIndex value="1"/>
+ </PosLookupRecord>
+ </PosRule>
+ </PosRuleSet>
+ </ContextPos>
</Lookup>
</LookupList>
</GPOS>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx
index b7e86ba2..eff24fc3 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_same.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
@@ -54,10 +54,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="uni0303"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="a"/>
</BaseCoverage>
<!-- ClassCount=1 -->
@@ -83,32 +83,32 @@
</MarkBasePos>
</Lookup>
<Lookup index="2">
- <LookupType value="8"/>
+ <LookupType value="7"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ChainContextPos index="0" Format="3">
- <!-- BacktrackGlyphCount=0 -->
- <!-- InputGlyphCount=3 -->
- <InputCoverage index="0" Format="1">
+ <ContextPos index="0" Format="1">
+ <Coverage>
<Glyph value="A"/>
- </InputCoverage>
- <InputCoverage index="1" Format="1">
- <Glyph value="a"/>
- </InputCoverage>
- <InputCoverage index="2" Format="1">
- <Glyph value="uni0303"/>
- </InputCoverage>
- <!-- LookAheadGlyphCount=0 -->
- <!-- PosCount=2 -->
- <PosLookupRecord index="0">
- <SequenceIndex value="0"/>
- <LookupListIndex value="0"/>
- </PosLookupRecord>
- <PosLookupRecord index="1">
- <SequenceIndex value="2"/>
- <LookupListIndex value="1"/>
- </PosLookupRecord>
- </ChainContextPos>
+ </Coverage>
+ <!-- PosRuleSetCount=1 -->
+ <PosRuleSet index="0">
+ <!-- PosRuleCount=1 -->
+ <PosRule index="0">
+ <!-- GlyphCount=3 -->
+ <!-- PosCount=2 -->
+ <Input index="0" value="a"/>
+ <Input index="1" value="uni0303"/>
+ <PosLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="0"/>
+ </PosLookupRecord>
+ <PosLookupRecord index="1">
+ <SequenceIndex value="2"/>
+ <LookupListIndex value="1"/>
+ </PosLookupRecord>
+ </PosRule>
+ </PosRuleSet>
+ </ContextPos>
</Lookup>
</LookupList>
</GPOS>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutMain.ttx b/Tests/varLib/data/test_results/InterpolateLayoutMain.ttx
index 6a0635d0..49d491fb 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutMain.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutMain.ttx
@@ -498,7 +498,7 @@
<GDEF>
<Version value="0x00010003"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="uni0024" class="1"/>
<ClassDef glyph="uni0024.nostroke" class="1"/>
<ClassDef glyph="uni0041" class="1"/>
diff --git a/Tests/varLib/data/test_results/InterpolateTestCFF2VF.ttx b/Tests/varLib/data/test_results/InterpolateTestCFF2VF.ttx
index e2d0f71c..949e6dad 100644
--- a/Tests/varLib/data/test_results/InterpolateTestCFF2VF.ttx
+++ b/Tests/varLib/data/test_results/InterpolateTestCFF2VF.ttx
@@ -24,6 +24,8 @@
<BlueFuzz value="0"/>
<StdHW value="28"/>
<StdVW value="34"/>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
</Private>
</FontDict>
</FDArray>
diff --git a/Tests/varLib/data/test_results/Mutator.ttx b/Tests/varLib/data/test_results/Mutator.ttx
index 75a0879e..71e5f28b 100644
--- a/Tests/varLib/data/test_results/Mutator.ttx
+++ b/Tests/varLib/data/test_results/Mutator.ttx
@@ -498,7 +498,7 @@
<GDEF>
<Version value="0x00010000"/>
- <GlyphClassDef Format="2">
+ <GlyphClassDef>
<ClassDef glyph="uni0024" class="1"/>
<ClassDef glyph="uni0024.nostroke" class="1"/>
<ClassDef glyph="uni0041" class="1"/>
diff --git a/Tests/varLib/data/test_results/SingleMaster.ttx b/Tests/varLib/data/test_results/SingleMaster.ttx
new file mode 100644
index 00000000..02cfe32b
--- /dev/null
+++ b/Tests/varLib/data/test_results/SingleMaster.ttx
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.0">
+
+ <GDEF>
+ <Version value="0x00010003"/>
+ <GlyphClassDef>
+ <ClassDef glyph="uni0024" class="1"/>
+ <ClassDef glyph="uni0024.nostroke" class="1"/>
+ <ClassDef glyph="uni0041" class="1"/>
+ <ClassDef glyph="uni0061" class="1"/>
+ </GlyphClassDef>
+ </GDEF>
+
+ <HVAR>
+ <Version value="0x00010000"/>
+ <VarStore Format="1">
+ <Format value="1"/>
+ <VarRegionList>
+ <!-- RegionAxisCount=1 -->
+ <!-- RegionCount=0 -->
+ </VarRegionList>
+ <!-- VarDataCount=1 -->
+ <VarData index="0">
+ <!-- ItemCount=6 -->
+ <NumShorts value="0"/>
+ <!-- VarRegionCount=0 -->
+ <Item index="0" value="[]"/>
+ <Item index="1" value="[]"/>
+ <Item index="2" value="[]"/>
+ <Item index="3" value="[]"/>
+ <Item index="4" value="[]"/>
+ <Item index="5" value="[]"/>
+ </VarData>
+ </VarStore>
+ </HVAR>
+
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=1 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=0 -->
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
+ <fvar>
+
+ <!-- Weight -->
+ <Axis>
+ <AxisTag>wght</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>400.0</MinValue>
+ <DefaultValue>400.0</DefaultValue>
+ <MaxValue>400.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+ </fvar>
+
+ <gvar>
+ <version value="1"/>
+ <reserved value="0"/>
+ </gvar>
+
+ <name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Test Family
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ Version 1.001;ADBO;Test Family Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Test Family
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.001
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ TestFamily-Master0
+ </namerecord>
+ <namerecord nameID="9" platformID="3" platEncID="1" langID="0x409">
+ Frank Grießhammer
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Master 0
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ </name>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/SparseMasters.ttx b/Tests/varLib/data/test_results/SparseMasters.ttx
index c2aa335c..a3f8e619 100644
--- a/Tests/varLib/data/test_results/SparseMasters.ttx
+++ b/Tests/varLib/data/test_results/SparseMasters.ttx
@@ -290,7 +290,7 @@
<GDEF>
<Version value="0x00010003"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="dotabovecomb" class="3"/>
<ClassDef glyph="e" class="1"/>
</GlyphClassDef>
@@ -352,10 +352,10 @@
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
- <MarkCoverage Format="1">
+ <MarkCoverage>
<Glyph value="dotabovecomb"/>
</MarkCoverage>
- <BaseCoverage Format="1">
+ <BaseCoverage>
<Glyph value="e"/>
</BaseCoverage>
<!-- ClassCount=1 -->
@@ -425,7 +425,7 @@
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <LigatureSubst index="0" Format="1">
+ <LigatureSubst index="0">
<LigatureSet glyph="a">
<Ligature components="e,s,s" glyph="s"/>
</LigatureSet>
@@ -572,6 +572,19 @@
<delta pt="21" x="0" y="0"/>
</tuple>
</glyphVariations>
+ <glyphVariations glyph="dotabovecomb">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="-8" y="28"/>
+ <delta pt="1" x="13" y="16"/>
+ <delta pt="2" x="17" y="-13"/>
+ <delta pt="3" x="-27" y="-20"/>
+ <delta pt="4" x="0" y="0"/>
+ <delta pt="5" x="0" y="0"/>
+ <delta pt="6" x="0" y="0"/>
+ <delta pt="7" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
<glyphVariations glyph="e">
<tuple>
<coord axis="wght" min="0.0" value="0.36365" max="1.0"/>
@@ -614,6 +627,12 @@
<delta pt="16" x="0" y="0"/>
</tuple>
</glyphVariations>
+ <glyphVariations glyph="edotabove">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="1" x="-6" y="91"/>
+ </tuple>
+ </glyphVariations>
<glyphVariations glyph="s">
<tuple>
<coord axis="wght" value="1.0"/>
@@ -635,25 +654,6 @@
<delta pt="15" x="0" y="0"/>
</tuple>
</glyphVariations>
- <glyphVariations glyph="dotabovecomb">
- <tuple>
- <coord axis="wght" value="1.0"/>
- <delta pt="0" x="-8" y="28"/>
- <delta pt="1" x="13" y="16"/>
- <delta pt="2" x="17" y="-13"/>
- <delta pt="3" x="-27" y="-20"/>
- <delta pt="4" x="0" y="0"/>
- <delta pt="5" x="0" y="0"/>
- <delta pt="6" x="0" y="0"/>
- <delta pt="7" x="0" y="0"/>
- </tuple>
- </glyphVariations>
- <glyphVariations glyph="edotabove">
- <tuple>
- <coord axis="wght" value="1.0"/>
- <delta pt="1" x="-6" y="91"/>
- </tuple>
- </glyphVariations>
</gvar>
</ttFont>
diff --git a/Tests/varLib/data/test_results/TestBASE.ttx b/Tests/varLib/data/test_results/TestBASE.ttx
new file mode 100644
index 00000000..23d9337b
--- /dev/null
+++ b/Tests/varLib/data/test_results/TestBASE.ttx
@@ -0,0 +1,477 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.4">
+
+ <BASE>
+ <Version value="0x00010001"/>
+ <HorizAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=5 -->
+ <BaselineTag index="0" value="icfb"/>
+ <BaselineTag index="1" value="icft"/>
+ <BaselineTag index="2" value="ideo"/>
+ <BaselineTag index="3" value="idtp"/>
+ <BaselineTag index="4" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=6 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="-75"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="835"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="-75"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="835"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="-75"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="835"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="hani"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="-75"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="835"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="4">
+ <BaseScriptTag value="kana"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="-75"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="835"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="5">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="-75"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="835"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="-120"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="880"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </HorizAxis>
+ <VertAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=5 -->
+ <BaselineTag index="0" value="icfb"/>
+ <BaselineTag index="1" value="icft"/>
+ <BaselineTag index="2" value="ideo"/>
+ <BaselineTag index="3" value="idtp"/>
+ <BaselineTag index="4" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=6 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="45"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="955"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="45"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="955"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="45"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="955"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="hani"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="45"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="955"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="4">
+ <BaseScriptTag value="kana"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="2"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="45"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="955"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="5">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="4"/>
+ <!-- BaseCoordCount=5 -->
+ <BaseCoord index="0" Format="3">
+ <Coordinate value="45"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="1" Format="3">
+ <Coordinate value="955"/>
+ <DeviceTable>
+ <StartSize value="0"/>
+ <EndSize value="1"/>
+ <DeltaFormat value="32768"/>
+ </DeviceTable>
+ </BaseCoord>
+ <BaseCoord index="2" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ <BaseCoord index="3" Format="1">
+ <Coordinate value="1000"/>
+ </BaseCoord>
+ <BaseCoord index="4" Format="1">
+ <Coordinate value="120"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </VertAxis>
+ <VarStore Format="1">
+ <Format value="1"/>
+ <VarRegionList>
+ <!-- RegionAxisCount=1 -->
+ <!-- RegionCount=1 -->
+ <Region index="0">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.0"/>
+ <PeakCoord value="1.0"/>
+ <EndCoord value="1.0"/>
+ </VarRegionAxis>
+ </Region>
+ </VarRegionList>
+ <!-- VarDataCount=1 -->
+ <VarData index="0">
+ <!-- ItemCount=2 -->
+ <NumShorts value="0"/>
+ <!-- VarRegionCount=1 -->
+ <VarRegionIndex index="0" value="0"/>
+ <Item index="0" value="[-17]"/>
+ <Item index="1" value="[17]"/>
+ </VarData>
+ </VarStore>
+ </BASE>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/TestNonMarkingCFF2.ttx b/Tests/varLib/data/test_results/TestNonMarkingCFF2.ttx
index 26bd7ba7..a78e6a6e 100644
--- a/Tests/varLib/data/test_results/TestNonMarkingCFF2.ttx
+++ b/Tests/varLib/data/test_results/TestNonMarkingCFF2.ttx
@@ -36,6 +36,8 @@
<StdVW>
<blend value="34 51"/>
</StdVW>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
</Private>
</FontDict>
</FDArray>
diff --git a/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx b/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
index f05f62f7..264a3d4a 100644
--- a/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
+++ b/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
@@ -138,6 +138,8 @@
<BlueFuzz value="0"/>
<StdHW value="1"/>
<StdVW value="1"/>
+ <LanguageGroup value="1"/>
+ <ExpansionFactor value="0.06"/>
</Private>
</FontDict>
<FontDict index="1">
@@ -148,6 +150,8 @@
<BlueFuzz value="0"/>
<StdHW value="1"/>
<StdVW value="1"/>
+ <LanguageGroup value="1"/>
+ <ExpansionFactor value="0.06"/>
</Private>
</FontDict>
<FontDict index="2">
@@ -156,6 +160,8 @@
<BlueScale value="0.039625"/>
<BlueShift value="7"/>
<BlueFuzz value="1"/>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
</Private>
</FontDict>
</FDArray>
@@ -290,7 +296,7 @@
</CharString>
<CharString name="cid06449" fdSelectIndex="1">
2 vsindex
- -60 30 203 30 -9 9 67 7 -7 14 -14 30 -20 20 80 30 59 30 121 30 18 93 -30 30 -30 108 -23 0 -26 67 2 76 -98 -2 -111 42 0 47 -13 0 -14 13 0 14 -33 0 -37 11 0 13 -11 0 -13 8 0 9 -7 0 -8 53 0 60 -32 0 -36 32 0 36 -52 0 -59 57 1 65 -33 0 -38 53 0 60 -83 -1 -93 54 0 60 -6 -19 -24 33 19 55 -76 -1 -86 76 1 86 -76 -1 -86 59 1 67 26 blend
+ -60 30 203 30 -9 9 67 7 -7 14 -14 30 -20 20 80 30 59 30 121 30 18 93 -30 30 -30 108 -23 0 -26 67 2 76 -98 -2 -111 42 0 47 -13 0 -14 13 0 14 -33 0 -37 11 0 13 -11 0 -13 8 0 8 -8 0 -8 53 0 60 -32 0 -36 32 0 36 -52 0 -59 57 1 65 -33 0 -38 53 0 60 -83 -1 -93 54 0 60 -6 -19 -24 33 19 55 -76 -1 -86 76 1 86 -76 -1 -86 59 1 67 26 blend
hstemhm
77 30 42 30 139 30 23 30 71 10 74 30 15 30 16 30 158 30 28 30 -4 29 -14 0 -16 88 1 99 -82 -1 -92 87 1 98 -130 -1 -146 102 1 114 -73 -1 -82 74 2 84 -112 -2 -126 27 0 30 13 0 15 90 1 101 -126 -1 -142 75 1 84 -68 -1 -76 102 1 115 -144 -1 -162 94 1 105 -79 -1 -88 95 1 106 -81 -1 -91 74 1 83 22 blend
vstemhm
diff --git a/Tests/varLib/data/test_results/test_vpal.ttx b/Tests/varLib/data/test_results/test_vpal.ttx
index 334ced55..be612930 100644
--- a/Tests/varLib/data/test_results/test_vpal.ttx
+++ b/Tests/varLib/data/test_results/test_vpal.ttx
@@ -34,7 +34,7 @@
<LookupFlag value="0"/>
<!-- SubTableCount=6 -->
<SinglePos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uniFF1A"/>
<Glyph value="uni3074"/>
</Coverage>
@@ -44,7 +44,7 @@
<Value index="1" XPlacement="0" YPlacement="0" XAdvance="-30"/>
</SinglePos>
<SinglePos index="1" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uni3001"/>
</Coverage>
<ValueFormat value="23"/>
@@ -57,7 +57,7 @@
</Value>
</SinglePos>
<SinglePos index="2" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uni30FB"/>
</Coverage>
<ValueFormat value="39"/>
@@ -70,7 +70,7 @@
</Value>
</SinglePos>
<SinglePos index="3" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uniFF2D"/>
</Coverage>
<ValueFormat value="87"/>
@@ -88,7 +88,7 @@
</Value>
</SinglePos>
<SinglePos index="4" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uni3073"/>
</Coverage>
<ValueFormat value="71"/>
@@ -101,7 +101,7 @@
</Value>
</SinglePos>
<SinglePos index="5" Format="1">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="uni307B"/>
</Coverage>
<ValueFormat value="23"/>
diff --git a/Tests/varLib/featureVars_test.py b/Tests/varLib/featureVars_test.py
index 4db2e625..89675af2 100644
--- a/Tests/varLib/featureVars_test.py
+++ b/Tests/varLib/featureVars_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.varLib.featureVars import (
overlayFeatureVariations)
diff --git a/Tests/varLib/instancer/conftest.py b/Tests/varLib/instancer/conftest.py
new file mode 100644
index 00000000..0ac8091d
--- /dev/null
+++ b/Tests/varLib/instancer/conftest.py
@@ -0,0 +1,13 @@
+import os
+from fontTools import ttLib
+import pytest
+
+
+TESTDATA = os.path.join(os.path.dirname(__file__), "data")
+
+
+@pytest.fixture
+def varfont():
+ f = ttLib.TTFont()
+ f.importXML(os.path.join(TESTDATA, "PartialInstancerTest-VF.ttx"))
+ return f
diff --git a/Tests/varLib/data/PartialInstancerTest-VF.ttx b/Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx
index 92540e03..268b5068 100644
--- a/Tests/varLib/data/PartialInstancerTest-VF.ttx
+++ b/Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx
@@ -479,6 +479,9 @@
<namerecord nameID="296" platformID="3" platEncID="1" langID="0x409">
TestVariableFont-XCdBd
</namerecord>
+ <namerecord nameID="297" platformID="3" platEncID="1" langID="0x409">
+ Normal
+ </namerecord>
</name>
<post>
@@ -764,6 +767,15 @@
<Value value="0.0"/>
<LinkedValue value="1.0"/>
</AxisValue>
+ <AxisValue index="3" Format="4">
+ <!-- AxisCount=1 -->
+ <Flags value="2"/>
+ <ValueNameID value="297"/> <!-- Normal -->
+ <AxisValueRecord index="0">
+ <AxisIndex value="1"/>
+ <Value value="100.0"/>
+ </AxisValueRecord>
+ </AxisValue>
</AxisValueArray>
<ElidedFallbackNameID value="2"/> <!-- Regular -->
</STAT>
diff --git a/Tests/varLib/data/PartialInstancerTest2-VF.ttx b/Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx
index 0f19bde3..cd7ffa05 100644
--- a/Tests/varLib/data/PartialInstancerTest2-VF.ttx
+++ b/Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.41">
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.0">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
@@ -14,16 +14,16 @@
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="2.001"/>
- <checkSumAdjustment value="0x6b1f158e"/>
+ <checkSumAdjustment value="0x605c3e60"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Tue Mar 15 19:50:39 2016"/>
- <modified value="Tue May 21 16:23:19 2019"/>
- <xMin value="-621"/>
- <yMin value="-389"/>
- <xMax value="2800"/>
- <yMax value="1067"/>
+ <modified value="Thu Oct 17 14:43:10 2019"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="638"/>
+ <yMax value="944"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="6"/>
<fontDirectionHint value="2"/>
@@ -36,10 +36,10 @@
<ascent value="1069"/>
<descent value="-293"/>
<lineGap value="0"/>
- <advanceWidthMax value="2840"/>
- <minLeftSideBearing value="-621"/>
- <minRightSideBearing value="-620"/>
- <xMaxExtent value="2800"/>
+ <advanceWidthMax value="639"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="1"/>
+ <xMaxExtent value="638"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
@@ -55,10 +55,10 @@
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="0x10000"/>
<numGlyphs value="5"/>
- <maxPoints value="202"/>
- <maxContours value="24"/>
- <maxCompositePoints value="315"/>
- <maxCompositeContours value="21"/>
+ <maxPoints value="19"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="32"/>
+ <maxCompositeContours value="3"/>
<maxZones value="1"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
@@ -66,8 +66,8 @@
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
- <maxComponentElements value="8"/>
- <maxComponentDepth value="8"/>
+ <maxComponentElements value="2"/>
+ <maxComponentDepth value="1"/>
</maxp>
<OS_2>
@@ -107,8 +107,8 @@
<ulUnicodeRange4 value="00000000 00010000 00000000 00000000"/>
<achVendID value="GOOG"/>
<fsSelection value="00000001 01000000"/>
- <usFirstCharIndex value="0"/>
- <usLastCharIndex value="65533"/>
+ <usFirstCharIndex value="65"/>
+ <usLastCharIndex value="192"/>
<sTypoAscender value="1069"/>
<sTypoDescender value="-293"/>
<sTypoLineGap value="0"/>
@@ -539,7 +539,7 @@
<GDEF>
<Version value="0x00010003"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="1"/>
@@ -547,13 +547,13 @@
<MarkGlyphSetsDef>
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=4 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
</Coverage>
- <Coverage index="3" Format="1">
+ <Coverage index="3">
</Coverage>
</MarkGlyphSetsDef>
<VarStore Format="1">
@@ -649,22 +649,22 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="T"/>
<Glyph value="Agrave"/>
</Coverage>
<ValueFormat1 value="68"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="2"/>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="2"/>
@@ -1037,15 +1037,104 @@
<Axis index="0">
<AxisTag value="wght"/>
<AxisNameID value="256"/> <!-- Weight -->
- <AxisOrdering value="0"/>
+ <AxisOrdering value="1"/>
</Axis>
<Axis index="1">
<AxisTag value="wdth"/>
<AxisNameID value="257"/> <!-- Width -->
- <AxisOrdering value="1"/>
+ <AxisOrdering value="0"/>
</Axis>
</DesignAxisRecord>
- <!-- AxisValueCount=0 -->
+ <!-- AxisValueCount=13 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="258"/> <!-- Thin -->
+ <Value value="100.0"/>
+ </AxisValue>
+ <AxisValue index="1" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="259"/> <!-- ExtraLight -->
+ <Value value="200.0"/>
+ </AxisValue>
+ <AxisValue index="2" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="260"/> <!-- Light -->
+ <Value value="300.0"/>
+ </AxisValue>
+ <AxisValue index="3" Format="3">
+ <AxisIndex value="0"/>
+ <Flags value="2"/>
+ <ValueNameID value="261"/> <!-- Regular -->
+ <Value value="400.0"/>
+ <LinkedValue value="700.0"/>
+ </AxisValue>
+ <AxisValue index="4" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="262"/> <!-- Medium -->
+ <Value value="500.0"/>
+ </AxisValue>
+ <AxisValue index="5" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="263"/> <!-- SemiBold -->
+ <Value value="600.0"/>
+ </AxisValue>
+ <AxisValue index="6" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="264"/> <!-- Bold -->
+ <Value value="700.0"/>
+ </AxisValue>
+ <AxisValue index="7" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="265"/> <!-- ExtraBold -->
+ <Value value="800.0"/>
+ </AxisValue>
+ <AxisValue index="8" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="266"/> <!-- Black -->
+ <Value value="900.0"/>
+ </AxisValue>
+ <AxisValue index="9" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="2"/>
+ <ValueNameID value="261"/> <!-- Regular -->
+ <NominalValue value="100.0"/>
+ <RangeMinValue value="93.75"/>
+ <RangeMaxValue value="100.0"/>
+ </AxisValue>
+ <AxisValue index="10" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="270"/> <!-- SemiCondensed -->
+ <NominalValue value="87.5"/>
+ <RangeMinValue value="81.25"/>
+ <RangeMaxValue value="93.75"/>
+ </AxisValue>
+ <AxisValue index="11" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="279"/> <!-- Condensed -->
+ <NominalValue value="75.0"/>
+ <RangeMinValue value="68.75"/>
+ <RangeMaxValue value="81.25"/>
+ </AxisValue>
+ <AxisValue index="12" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="288"/> <!-- ExtraCondensed -->
+ <NominalValue value="62.5"/>
+ <RangeMinValue value="62.5"/>
+ <RangeMaxValue value="68.75"/>
+ </AxisValue>
+ </AxisValueArray>
<ElidedFallbackNameID value="2"/> <!-- Regular -->
</STAT>
diff --git a/Tests/varLib/instancer/data/PartialInstancerTest3-VF.ttx b/Tests/varLib/instancer/data/PartialInstancerTest3-VF.ttx
new file mode 100644
index 00000000..01c7d050
--- /dev/null
+++ b/Tests/varLib/instancer/data/PartialInstancerTest3-VF.ttx
@@ -0,0 +1,439 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.15">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="B"/>
+ <GlyphID id="3" name="C"/>
+ <GlyphID id="4" name="D"/>
+ <GlyphID id="5" name="E"/>
+ <GlyphID id="6" name="F"/>
+ <GlyphID id="7" name="space"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0xc3d4abe6"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Sep 23 12:54:22 2020"/>
+ <modified value="Tue Sep 29 18:06:03 2020"/>
+ <xMin value="-152"/>
+ <yMin value="-200"/>
+ <xMax value="1059"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="-152"/>
+ <minRightSideBearing value="-559"/>
+ <xMaxExtent value="1059"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="8"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="8"/>
+ <maxPoints value="8"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="12"/>
+ <maxCompositeContours value="3"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="3"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="513"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="70"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="500" lsb="153"/>
+ <mtx name="B" width="500" lsb="-152"/>
+ <mtx name="C" width="500" lsb="-133"/>
+ <mtx name="D" width="500" lsb="-97"/>
+ <mtx name="E" width="500" lsb="-87"/>
+ <mtx name="F" width="500" lsb="-107"/>
+ <mtx name="space" width="600" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ <map code="0x45" name="E"/><!-- LATIN CAPITAL LETTER E -->
+ <map code="0x46" name="F"/><!-- LATIN CAPITAL LETTER F -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ <map code="0x45" name="E"/><!-- LATIN CAPITAL LETTER E -->
+ <map code="0x46" name="F"/><!-- LATIN CAPITAL LETTER F -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="153" yMin="-66" xMax="350" yMax="646">
+ <contour>
+ <pt x="153" y="646" on="1"/>
+ <pt x="350" y="646" on="1"/>
+ <pt x="350" y="-66" on="1"/>
+ <pt x="153" y="-66" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="B" xMin="-152" yMin="39" xMax="752" yMax="592">
+ <contour>
+ <pt x="-152" y="448" on="1"/>
+ <pt x="752" y="448" on="1"/>
+ <pt x="752" y="215" on="1"/>
+ <pt x="-152" y="215" on="1"/>
+ </contour>
+ <contour>
+ <pt x="129" y="592" on="1"/>
+ <pt x="401" y="592" on="1"/>
+ <pt x="401" y="39" on="1"/>
+ <pt x="129" y="39" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="C" xMin="-133" yMin="-66" xMax="771" yMax="646">
+ <component glyphName="A" x="-250" y="0" flags="0x4"/>
+ <component glyphName="B" x="19" y="-28" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="D" xMin="-97" yMin="-66" xMax="1059" yMax="646">
+ <component glyphName="A" x="-250" y="0" flags="0x4"/>
+ <component glyphName="B" x="307" y="-28" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="E" xMin="-87" yMin="-87" xMax="801" yMax="650">
+ <component glyphName="A" x="450" y="-77" scalex="0.9397" scale01="0.34204" scale10="-0.34204" scaley="0.9397" flags="0x4"/>
+ <component glyphName="A" x="8" y="4" flags="0x4"/>
+ <component glyphName="A" x="-240" y="0" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="F" xMin="-107" yMin="-95" xMax="837" yMax="650">
+ <component glyphName="A" x="501" y="-114" scalex="0.866" scale01="0.5" scale10="-0.5" scaley="0.866" flags="0x4"/>
+ <component glyphName="A" x="-12" y="4" flags="0x4"/>
+ <component glyphName="A" x="-260" y="0" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Remove Overlaps Test
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;RemoveOverlapsTest-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Remove Overlaps Test Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ RemoveOverlapsTest-Regular
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ </extraNames>
+ </post>
+
+ <HVAR>
+ <Version value="0x00010000"/>
+ <VarStore Format="1">
+ <Format value="1"/>
+ <VarRegionList>
+ <!-- RegionAxisCount=1 -->
+ <!-- RegionCount=1 -->
+ <Region index="0">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.0"/>
+ <PeakCoord value="1.0"/>
+ <EndCoord value="1.0"/>
+ </VarRegionAxis>
+ </Region>
+ </VarRegionList>
+ <!-- VarDataCount=1 -->
+ <VarData index="0">
+ <!-- ItemCount=8 -->
+ <NumShorts value="0"/>
+ <!-- VarRegionCount=0 -->
+ <Item index="0" value="[]"/>
+ <Item index="1" value="[]"/>
+ <Item index="2" value="[]"/>
+ <Item index="3" value="[]"/>
+ <Item index="4" value="[]"/>
+ <Item index="5" value="[]"/>
+ <Item index="6" value="[]"/>
+ <Item index="7" value="[]"/>
+ </VarData>
+ </VarStore>
+ </HVAR>
+
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=1 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=0 -->
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
+ <fvar>
+
+ <!-- Weight -->
+ <Axis>
+ <AxisTag>wght</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>400.0</MinValue>
+ <DefaultValue>400.0</DefaultValue>
+ <MaxValue>700.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+
+ <!-- Regular -->
+ <NamedInstance flags="0x0" subfamilyNameID="257">
+ <coord axis="wght" value="400.0"/>
+ </NamedInstance>
+
+ <!-- Regular -->
+ <NamedInstance flags="0x0" subfamilyNameID="257">
+ <coord axis="wght" value="700.0"/>
+ </NamedInstance>
+ </fvar>
+
+ <gvar>
+ <version value="1"/>
+ <reserved value="0"/>
+ <glyphVariations glyph="A">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="-40" y="0"/>
+ <delta pt="1" x="40" y="0"/>
+ <delta pt="2" x="40" y="0"/>
+ <delta pt="3" x="-40" y="0"/>
+ <delta pt="4" x="0" y="0"/>
+ <delta pt="5" x="0" y="0"/>
+ <delta pt="6" x="0" y="0"/>
+ <delta pt="7" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="B">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="-40" y="0"/>
+ <delta pt="2" x="40" y="0"/>
+ <delta pt="5" x="40" y="20"/>
+ <delta pt="7" x="-40" y="-20"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="C">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="0"/>
+ <delta pt="2" x="0" y="0"/>
+ <delta pt="3" x="0" y="0"/>
+ <delta pt="4" x="0" y="0"/>
+ <delta pt="5" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="D">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="0"/>
+ <delta pt="2" x="0" y="0"/>
+ <delta pt="3" x="0" y="0"/>
+ <delta pt="4" x="0" y="0"/>
+ <delta pt="5" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="E">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="0"/>
+ <delta pt="2" x="0" y="0"/>
+ <delta pt="3" x="0" y="0"/>
+ <delta pt="4" x="0" y="0"/>
+ <delta pt="5" x="0" y="0"/>
+ <delta pt="6" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="F">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="0"/>
+ <delta pt="2" x="0" y="0"/>
+ <delta pt="3" x="0" y="0"/>
+ <delta pt="4" x="0" y="0"/>
+ <delta pt="5" x="0" y="0"/>
+ <delta pt="6" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ </gvar>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx
index c64049fe..776a92f1 100644
--- a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx
@@ -14,12 +14,12 @@
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="2.001"/>
- <checkSumAdjustment value="0x982d27a8"/>
+ <checkSumAdjustment value="0x90f1c28"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Tue Mar 15 19:50:39 2016"/>
- <modified value="Tue May 21 16:23:19 2019"/>
+ <modified value="Thu Oct 17 14:43:10 2019"/>
<xMin value="0"/>
<yMin value="0"/>
<xMax value="577"/>
@@ -238,6 +238,18 @@
</glyf>
<name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Width
+ </namerecord>
+ <namerecord nameID="258" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Thin
+ </namerecord>
+ <namerecord nameID="261" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
<namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
Copyright 2015 Google Inc. All Rights Reserved.
</namerecord>
@@ -283,6 +295,18 @@
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
http://scripts.sil.org/OFL
</namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ Width
+ </namerecord>
+ <namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
+ Thin
+ </namerecord>
+ <namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
</name>
<post>
@@ -311,7 +335,7 @@
<GDEF>
<Version value="0x00010002"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="1"/>
@@ -319,13 +343,13 @@
<MarkGlyphSetsDef>
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=4 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
</Coverage>
- <Coverage index="3" Format="1">
+ <Coverage index="3">
</Coverage>
</MarkGlyphSetsDef>
</GDEF>
@@ -393,20 +417,20 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="T"/>
<Glyph value="Agrave"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="T" class="1"/>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="2"/>
@@ -481,4 +505,40 @@
</LookupList>
</GSUB>
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=2 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="1"/>
+ </Axis>
+ <Axis index="1">
+ <AxisTag value="wdth"/>
+ <AxisNameID value="257"/> <!-- Width -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=2 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="258"/> <!-- Thin -->
+ <Value value="100.0"/>
+ </AxisValue>
+ <AxisValue index="1" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="261"/> <!-- Regular -->
+ <NominalValue value="100.0"/>
+ <RangeMinValue value="93.75"/>
+ <RangeMaxValue value="100.0"/>
+ </AxisValue>
+ </AxisValueArray>
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
</ttFont>
diff --git a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-100,62.5.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,62.5.ttx
index 87d0c65c..61bc41cc 100644
--- a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-100,62.5.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,62.5.ttx
@@ -14,12 +14,12 @@
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="2.001"/>
- <checkSumAdjustment value="0x1d4f3a2e"/>
+ <checkSumAdjustment value="0x31525751"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Tue Mar 15 19:50:39 2016"/>
- <modified value="Tue May 21 16:23:19 2019"/>
+ <modified value="Thu Oct 17 14:43:10 2019"/>
<xMin value="0"/>
<yMin value="0"/>
<xMax value="496"/>
@@ -238,6 +238,18 @@
</glyf>
<name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Width
+ </namerecord>
+ <namerecord nameID="258" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Thin
+ </namerecord>
+ <namerecord nameID="288" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ ExtraCondensed
+ </namerecord>
<namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
Copyright 2015 Google Inc. All Rights Reserved.
</namerecord>
@@ -283,6 +295,18 @@
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
http://scripts.sil.org/OFL
</namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ Width
+ </namerecord>
+ <namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
+ Thin
+ </namerecord>
+ <namerecord nameID="288" platformID="3" platEncID="1" langID="0x409">
+ ExtraCondensed
+ </namerecord>
</name>
<post>
@@ -311,7 +335,7 @@
<GDEF>
<Version value="0x00010002"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="1"/>
@@ -319,13 +343,13 @@
<MarkGlyphSetsDef>
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=4 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
</Coverage>
- <Coverage index="3" Format="1">
+ <Coverage index="3">
</Coverage>
</MarkGlyphSetsDef>
</GDEF>
@@ -393,20 +417,20 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="T"/>
<Glyph value="Agrave"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="T" class="1"/>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="2"/>
@@ -481,4 +505,40 @@
</LookupList>
</GSUB>
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=2 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="1"/>
+ </Axis>
+ <Axis index="1">
+ <AxisTag value="wdth"/>
+ <AxisNameID value="257"/> <!-- Width -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=2 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="258"/> <!-- Thin -->
+ <Value value="100.0"/>
+ </AxisValue>
+ <AxisValue index="1" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="288"/> <!-- ExtraCondensed -->
+ <NominalValue value="62.5"/>
+ <RangeMinValue value="62.5"/>
+ <RangeMaxValue value="68.75"/>
+ </AxisValue>
+ </AxisValueArray>
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
</ttFont>
diff --git a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx
index fc64365e..c2d20571 100644
--- a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx
@@ -14,12 +14,12 @@
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="2.001"/>
- <checkSumAdjustment value="0xf43664b4"/>
+ <checkSumAdjustment value="0x4b2d3480"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Tue Mar 15 19:50:39 2016"/>
- <modified value="Tue May 21 16:23:19 2019"/>
+ <modified value="Thu Oct 17 14:43:10 2019"/>
<xMin value="0"/>
<yMin value="0"/>
<xMax value="638"/>
@@ -238,6 +238,15 @@
</glyf>
<name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Width
+ </namerecord>
+ <namerecord nameID="261" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
<namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
Copyright 2015 Google Inc. All Rights Reserved.
</namerecord>
@@ -283,6 +292,15 @@
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
http://scripts.sil.org/OFL
</namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ Width
+ </namerecord>
+ <namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
</name>
<post>
@@ -311,7 +329,7 @@
<GDEF>
<Version value="0x00010002"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="1"/>
@@ -319,13 +337,13 @@
<MarkGlyphSetsDef>
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=4 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
</Coverage>
- <Coverage index="3" Format="1">
+ <Coverage index="3">
</Coverage>
</MarkGlyphSetsDef>
</GDEF>
@@ -393,20 +411,20 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="T"/>
<Glyph value="Agrave"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="T" class="1"/>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="2"/>
@@ -481,4 +499,41 @@
</LookupList>
</GSUB>
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=2 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="1"/>
+ </Axis>
+ <Axis index="1">
+ <AxisTag value="wdth"/>
+ <AxisNameID value="257"/> <!-- Width -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=2 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="3">
+ <AxisIndex value="0"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="261"/> <!-- Regular -->
+ <Value value="400.0"/>
+ <LinkedValue value="700.0"/>
+ </AxisValue>
+ <AxisValue index="1" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="261"/> <!-- Regular -->
+ <NominalValue value="100.0"/>
+ <RangeMinValue value="93.75"/>
+ <RangeMaxValue value="100.0"/>
+ </AxisValue>
+ </AxisValueArray>
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
</ttFont>
diff --git a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-400,62.5.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,62.5.ttx
index 9b40106f..63eeb0e7 100644
--- a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-400,62.5.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,62.5.ttx
@@ -14,12 +14,12 @@
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="2.001"/>
- <checkSumAdjustment value="0xd9290bac"/>
+ <checkSumAdjustment value="0x39ab2622"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Tue Mar 15 19:50:39 2016"/>
- <modified value="Tue May 21 16:23:19 2019"/>
+ <modified value="Thu Oct 17 14:43:10 2019"/>
<xMin value="0"/>
<yMin value="0"/>
<xMax value="496"/>
@@ -238,6 +238,18 @@
</glyf>
<name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Width
+ </namerecord>
+ <namerecord nameID="261" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="288" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ ExtraCondensed
+ </namerecord>
<namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
Copyright 2015 Google Inc. All Rights Reserved.
</namerecord>
@@ -283,6 +295,18 @@
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
http://scripts.sil.org/OFL
</namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ Width
+ </namerecord>
+ <namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="288" platformID="3" platEncID="1" langID="0x409">
+ ExtraCondensed
+ </namerecord>
</name>
<post>
@@ -311,7 +335,7 @@
<GDEF>
<Version value="0x00010002"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="1"/>
@@ -319,13 +343,13 @@
<MarkGlyphSetsDef>
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=4 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
</Coverage>
- <Coverage index="3" Format="1">
+ <Coverage index="3">
</Coverage>
</MarkGlyphSetsDef>
</GDEF>
@@ -393,20 +417,20 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="T"/>
<Glyph value="Agrave"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="T" class="1"/>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="2"/>
@@ -481,4 +505,41 @@
</LookupList>
</GSUB>
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=2 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="1"/>
+ </Axis>
+ <Axis index="1">
+ <AxisTag value="wdth"/>
+ <AxisNameID value="257"/> <!-- Width -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=2 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="3">
+ <AxisIndex value="0"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="261"/> <!-- Regular -->
+ <Value value="400.0"/>
+ <LinkedValue value="700.0"/>
+ </AxisValue>
+ <AxisValue index="1" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="288"/> <!-- ExtraCondensed -->
+ <NominalValue value="62.5"/>
+ <RangeMinValue value="62.5"/>
+ <RangeMaxValue value="68.75"/>
+ </AxisValue>
+ </AxisValueArray>
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
</ttFont>
diff --git a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx
index 8f851796..013ba1e7 100644
--- a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx
@@ -14,12 +14,12 @@
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="2.001"/>
- <checkSumAdjustment value="0xa514fda"/>
+ <checkSumAdjustment value="0x7b5e7903"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Tue Mar 15 19:50:39 2016"/>
- <modified value="Tue May 21 16:23:19 2019"/>
+ <modified value="Thu Oct 17 14:43:10 2019"/>
<xMin value="0"/>
<yMin value="0"/>
<xMax value="726"/>
@@ -238,6 +238,18 @@
</glyf>
<name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Width
+ </namerecord>
+ <namerecord nameID="261" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="266" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Black
+ </namerecord>
<namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
Copyright 2015 Google Inc. All Rights Reserved.
</namerecord>
@@ -283,6 +295,18 @@
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
http://scripts.sil.org/OFL
</namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ Width
+ </namerecord>
+ <namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="266" platformID="3" platEncID="1" langID="0x409">
+ Black
+ </namerecord>
</name>
<post>
@@ -311,7 +335,7 @@
<GDEF>
<Version value="0x00010002"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="1"/>
@@ -319,13 +343,13 @@
<MarkGlyphSetsDef>
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=4 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
</Coverage>
- <Coverage index="3" Format="1">
+ <Coverage index="3">
</Coverage>
</MarkGlyphSetsDef>
</GDEF>
@@ -393,20 +417,20 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="T"/>
<Glyph value="Agrave"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="T" class="1"/>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="2"/>
@@ -481,4 +505,40 @@
</LookupList>
</GSUB>
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=2 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="1"/>
+ </Axis>
+ <Axis index="1">
+ <AxisTag value="wdth"/>
+ <AxisNameID value="257"/> <!-- Width -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=2 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="266"/> <!-- Black -->
+ <Value value="900.0"/>
+ </AxisValue>
+ <AxisValue index="1" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="261"/> <!-- Regular -->
+ <NominalValue value="100.0"/>
+ <RangeMinValue value="93.75"/>
+ <RangeMaxValue value="100.0"/>
+ </AxisValue>
+ </AxisValueArray>
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
</ttFont>
diff --git a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-900,62.5.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,62.5.ttx
index bc8c7e9a..45e34cbf 100644
--- a/Tests/varLib/data/test_results/PartialInstancerTest2-VF-instance-900,62.5.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,62.5.ttx
@@ -14,12 +14,12 @@
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="2.001"/>
- <checkSumAdjustment value="0xc8e8b846"/>
+ <checkSumAdjustment value="0x7f9149e4"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Tue Mar 15 19:50:39 2016"/>
- <modified value="Tue May 21 16:23:19 2019"/>
+ <modified value="Thu Oct 17 14:43:10 2019"/>
<xMin value="0"/>
<yMin value="0"/>
<xMax value="574"/>
@@ -238,6 +238,18 @@
</glyf>
<name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Width
+ </namerecord>
+ <namerecord nameID="266" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Black
+ </namerecord>
+ <namerecord nameID="288" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ ExtraCondensed
+ </namerecord>
<namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
Copyright 2015 Google Inc. All Rights Reserved.
</namerecord>
@@ -283,6 +295,18 @@
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
http://scripts.sil.org/OFL
</namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ Width
+ </namerecord>
+ <namerecord nameID="266" platformID="3" platEncID="1" langID="0x409">
+ Black
+ </namerecord>
+ <namerecord nameID="288" platformID="3" platEncID="1" langID="0x409">
+ ExtraCondensed
+ </namerecord>
</name>
<post>
@@ -311,7 +335,7 @@
<GDEF>
<Version value="0x00010002"/>
- <GlyphClassDef Format="1">
+ <GlyphClassDef>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="1"/>
@@ -319,13 +343,13 @@
<MarkGlyphSetsDef>
<MarkSetTableFormat value="1"/>
<!-- MarkSetCount=4 -->
- <Coverage index="0" Format="1">
+ <Coverage index="0">
</Coverage>
- <Coverage index="1" Format="1">
+ <Coverage index="1">
</Coverage>
- <Coverage index="2" Format="1">
+ <Coverage index="2">
</Coverage>
- <Coverage index="3" Format="1">
+ <Coverage index="3">
</Coverage>
</MarkGlyphSetsDef>
</GDEF>
@@ -393,20 +417,20 @@
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
- <LookupFlag value="8"/>
+ <LookupFlag value="8"/><!-- ignoreMarks -->
<!-- SubTableCount=1 -->
<PairPos index="0" Format="2">
- <Coverage Format="1">
+ <Coverage>
<Glyph value="A"/>
<Glyph value="T"/>
<Glyph value="Agrave"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
- <ClassDef1 Format="1">
+ <ClassDef1>
<ClassDef glyph="T" class="1"/>
</ClassDef1>
- <ClassDef2 Format="1">
+ <ClassDef2>
<ClassDef glyph="A" class="1"/>
<ClassDef glyph="Agrave" class="1"/>
<ClassDef glyph="T" class="2"/>
@@ -481,4 +505,40 @@
</LookupList>
</GSUB>
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=2 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="1"/>
+ </Axis>
+ <Axis index="1">
+ <AxisTag value="wdth"/>
+ <AxisNameID value="257"/> <!-- Width -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=2 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="266"/> <!-- Black -->
+ <Value value="900.0"/>
+ </AxisValue>
+ <AxisValue index="1" Format="2">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="288"/> <!-- ExtraCondensed -->
+ <NominalValue value="62.5"/>
+ <RangeMinValue value="62.5"/>
+ <RangeMaxValue value="68.75"/>
+ </AxisValue>
+ </AxisValueArray>
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
</ttFont>
diff --git a/Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-400-no-overlap-flags.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-400-no-overlap-flags.ttx
new file mode 100644
index 00000000..fc6310d5
--- /dev/null
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-400-no-overlap-flags.ttx
@@ -0,0 +1,305 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.15">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="B"/>
+ <GlyphID id="3" name="C"/>
+ <GlyphID id="4" name="D"/>
+ <GlyphID id="5" name="E"/>
+ <GlyphID id="6" name="F"/>
+ <GlyphID id="7" name="space"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x98c89e17"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Sep 23 12:54:22 2020"/>
+ <modified value="Tue Sep 29 18:06:03 2020"/>
+ <xMin value="-152"/>
+ <yMin value="-200"/>
+ <xMax value="1059"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="-152"/>
+ <minRightSideBearing value="-559"/>
+ <xMaxExtent value="1059"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="8"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="8"/>
+ <maxPoints value="8"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="12"/>
+ <maxCompositeContours value="3"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="3"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="513"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="70"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="500" lsb="153"/>
+ <mtx name="B" width="500" lsb="-152"/>
+ <mtx name="C" width="500" lsb="-133"/>
+ <mtx name="D" width="500" lsb="-97"/>
+ <mtx name="E" width="500" lsb="-87"/>
+ <mtx name="F" width="500" lsb="-107"/>
+ <mtx name="space" width="600" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ <map code="0x45" name="E"/><!-- LATIN CAPITAL LETTER E -->
+ <map code="0x46" name="F"/><!-- LATIN CAPITAL LETTER F -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ <map code="0x45" name="E"/><!-- LATIN CAPITAL LETTER E -->
+ <map code="0x46" name="F"/><!-- LATIN CAPITAL LETTER F -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="153" yMin="-66" xMax="350" yMax="646">
+ <contour>
+ <pt x="153" y="646" on="1"/>
+ <pt x="350" y="646" on="1"/>
+ <pt x="350" y="-66" on="1"/>
+ <pt x="153" y="-66" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="B" xMin="-152" yMin="39" xMax="752" yMax="592">
+ <contour>
+ <pt x="-152" y="448" on="1"/>
+ <pt x="752" y="448" on="1"/>
+ <pt x="752" y="215" on="1"/>
+ <pt x="-152" y="215" on="1"/>
+ </contour>
+ <contour>
+ <pt x="129" y="592" on="1"/>
+ <pt x="401" y="592" on="1"/>
+ <pt x="401" y="39" on="1"/>
+ <pt x="129" y="39" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="C" xMin="-133" yMin="-66" xMax="771" yMax="646">
+ <component glyphName="A" x="-250" y="0" flags="0x4"/>
+ <component glyphName="B" x="19" y="-28" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="D" xMin="-97" yMin="-66" xMax="1059" yMax="646">
+ <component glyphName="A" x="-250" y="0" flags="0x4"/>
+ <component glyphName="B" x="307" y="-28" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="E" xMin="-87" yMin="-87" xMax="801" yMax="650">
+ <component glyphName="A" x="450" y="-77" scalex="0.9397" scale01="0.34204" scale10="-0.34204" scaley="0.9397" flags="0x4"/>
+ <component glyphName="A" x="8" y="4" flags="0x4"/>
+ <component glyphName="A" x="-240" y="0" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="F" xMin="-107" yMin="-95" xMax="837" yMax="650">
+ <component glyphName="A" x="501" y="-114" scalex="0.866" scale01="0.5" scale10="-0.5" scaley="0.866" flags="0x4"/>
+ <component glyphName="A" x="-12" y="4" flags="0x4"/>
+ <component glyphName="A" x="-260" y="0" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Remove Overlaps Test
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;RemoveOverlapsTest-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Remove Overlaps Test Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ RemoveOverlapsTest-Regular
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ </extraNames>
+ </post>
+
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=1 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=0 -->
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
+</ttFont>
diff --git a/Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-400-no-overlaps.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-400-no-overlaps.ttx
new file mode 100644
index 00000000..3e18c9b7
--- /dev/null
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-400-no-overlaps.ttx
@@ -0,0 +1,343 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.15">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="B"/>
+ <GlyphID id="3" name="C"/>
+ <GlyphID id="4" name="D"/>
+ <GlyphID id="5" name="E"/>
+ <GlyphID id="6" name="F"/>
+ <GlyphID id="7" name="space"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x1cd9cd87"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Sep 23 12:54:22 2020"/>
+ <modified value="Tue Sep 29 18:06:03 2020"/>
+ <xMin value="-152"/>
+ <yMin value="-200"/>
+ <xMax value="1059"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="-152"/>
+ <minRightSideBearing value="-559"/>
+ <xMaxExtent value="1059"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="8"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="8"/>
+ <maxPoints value="20"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="16"/>
+ <maxCompositeContours value="3"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="3"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="513"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="70"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="500" lsb="153"/>
+ <mtx name="B" width="500" lsb="-152"/>
+ <mtx name="C" width="500" lsb="-133"/>
+ <mtx name="D" width="500" lsb="-97"/>
+ <mtx name="E" width="500" lsb="-87"/>
+ <mtx name="F" width="500" lsb="-107"/>
+ <mtx name="space" width="600" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ <map code="0x45" name="E"/><!-- LATIN CAPITAL LETTER E -->
+ <map code="0x46" name="F"/><!-- LATIN CAPITAL LETTER F -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ <map code="0x45" name="E"/><!-- LATIN CAPITAL LETTER E -->
+ <map code="0x46" name="F"/><!-- LATIN CAPITAL LETTER F -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="153" yMin="-66" xMax="350" yMax="646">
+ <contour>
+ <pt x="153" y="646" on="1"/>
+ <pt x="350" y="646" on="1"/>
+ <pt x="350" y="-66" on="1"/>
+ <pt x="153" y="-66" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="B" xMin="-152" yMin="39" xMax="752" yMax="592">
+ <contour>
+ <pt x="-152" y="448" on="1"/>
+ <pt x="129" y="448" on="1"/>
+ <pt x="129" y="592" on="1"/>
+ <pt x="401" y="592" on="1"/>
+ <pt x="401" y="448" on="1"/>
+ <pt x="752" y="448" on="1"/>
+ <pt x="752" y="215" on="1"/>
+ <pt x="401" y="215" on="1"/>
+ <pt x="401" y="39" on="1"/>
+ <pt x="129" y="39" on="1"/>
+ <pt x="129" y="215" on="1"/>
+ <pt x="-152" y="215" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="C" xMin="-133" yMin="-66" xMax="771" yMax="646">
+ <contour>
+ <pt x="-97" y="646" on="1"/>
+ <pt x="100" y="646" on="1"/>
+ <pt x="100" y="420" on="1"/>
+ <pt x="148" y="420" on="1"/>
+ <pt x="148" y="564" on="1"/>
+ <pt x="420" y="564" on="1"/>
+ <pt x="420" y="420" on="1"/>
+ <pt x="771" y="420" on="1"/>
+ <pt x="771" y="187" on="1"/>
+ <pt x="420" y="187" on="1"/>
+ <pt x="420" y="11" on="1"/>
+ <pt x="148" y="11" on="1"/>
+ <pt x="148" y="187" on="1"/>
+ <pt x="100" y="187" on="1"/>
+ <pt x="100" y="-66" on="1"/>
+ <pt x="-97" y="-66" on="1"/>
+ <pt x="-97" y="187" on="1"/>
+ <pt x="-133" y="187" on="1"/>
+ <pt x="-133" y="420" on="1"/>
+ <pt x="-97" y="420" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="D" xMin="-97" yMin="-66" xMax="1059" yMax="646">
+ <component glyphName="A" x="-250" y="0" flags="0x4"/>
+ <component glyphName="B" x="307" y="-28" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="E" xMin="-87" yMin="-87" xMax="801" yMax="650">
+ <component glyphName="A" x="450" y="-77" scalex="0.9397" scale01="0.34204" scale10="-0.34204" scaley="0.9397" flags="0x4"/>
+ <component glyphName="A" x="8" y="4" flags="0x4"/>
+ <component glyphName="A" x="-240" y="0" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="F" xMin="-107" yMin="-95" xMax="837" yMax="650">
+ <contour>
+ <pt x="141" y="650" on="1"/>
+ <pt x="338" y="650" on="1"/>
+ <pt x="338" y="538" on="1"/>
+ <pt x="481" y="620" on="1"/>
+ <pt x="837" y="4" on="1"/>
+ <pt x="667" y="-95" on="1"/>
+ <pt x="338" y="474" on="1"/>
+ <pt x="338" y="-62" on="1"/>
+ <pt x="141" y="-62" on="1"/>
+ </contour>
+ <contour>
+ <pt x="-107" y="646" on="1"/>
+ <pt x="90" y="646" on="1"/>
+ <pt x="90" y="-66" on="1"/>
+ <pt x="-107" y="-66" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Remove Overlaps Test
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;RemoveOverlapsTest-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Remove Overlaps Test Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ RemoveOverlapsTest-Regular
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ </extraNames>
+ </post>
+
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=1 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=0 -->
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
+</ttFont>
diff --git a/Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-700-no-overlaps.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-700-no-overlaps.ttx
new file mode 100644
index 00000000..be0353da
--- /dev/null
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest3-VF-instance-700-no-overlaps.ttx
@@ -0,0 +1,367 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.15">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="B"/>
+ <GlyphID id="3" name="C"/>
+ <GlyphID id="4" name="D"/>
+ <GlyphID id="5" name="E"/>
+ <GlyphID id="6" name="F"/>
+ <GlyphID id="7" name="space"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0xc12af6d1"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Sep 23 12:54:22 2020"/>
+ <modified value="Tue Sep 29 18:06:03 2020"/>
+ <xMin value="-192"/>
+ <yMin value="-200"/>
+ <xMax value="1099"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="-192"/>
+ <minRightSideBearing value="-599"/>
+ <xMaxExtent value="1099"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="8"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="8"/>
+ <maxPoints value="16"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="513"/>
+ <usWeightClass value="700"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="70"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="500" lsb="113"/>
+ <mtx name="B" width="500" lsb="-192"/>
+ <mtx name="C" width="500" lsb="-173"/>
+ <mtx name="D" width="500" lsb="-137"/>
+ <mtx name="E" width="500" lsb="-127"/>
+ <mtx name="F" width="500" lsb="-147"/>
+ <mtx name="space" width="600" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ <map code="0x45" name="E"/><!-- LATIN CAPITAL LETTER E -->
+ <map code="0x46" name="F"/><!-- LATIN CAPITAL LETTER F -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="space"/><!-- SPACE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ <map code="0x45" name="E"/><!-- LATIN CAPITAL LETTER E -->
+ <map code="0x46" name="F"/><!-- LATIN CAPITAL LETTER F -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="113" yMin="-66" xMax="390" yMax="646">
+ <contour>
+ <pt x="113" y="646" on="1"/>
+ <pt x="390" y="646" on="1"/>
+ <pt x="390" y="-66" on="1"/>
+ <pt x="113" y="-66" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="B" xMin="-192" yMin="19" xMax="792" yMax="612">
+ <contour>
+ <pt x="-192" y="448" on="1"/>
+ <pt x="89" y="448" on="1"/>
+ <pt x="89" y="612" on="1"/>
+ <pt x="441" y="612" on="1"/>
+ <pt x="441" y="448" on="1"/>
+ <pt x="792" y="448" on="1"/>
+ <pt x="792" y="215" on="1"/>
+ <pt x="441" y="215" on="1"/>
+ <pt x="441" y="19" on="1"/>
+ <pt x="89" y="19" on="1"/>
+ <pt x="89" y="215" on="1"/>
+ <pt x="-192" y="215" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="C" xMin="-173" yMin="-66" xMax="811" yMax="646">
+ <contour>
+ <pt x="-137" y="646" on="1"/>
+ <pt x="140" y="646" on="1"/>
+ <pt x="140" y="584" on="1"/>
+ <pt x="460" y="584" on="1"/>
+ <pt x="460" y="420" on="1"/>
+ <pt x="811" y="420" on="1"/>
+ <pt x="811" y="187" on="1"/>
+ <pt x="460" y="187" on="1"/>
+ <pt x="460" y="-9" on="1"/>
+ <pt x="140" y="-9" on="1"/>
+ <pt x="140" y="-66" on="1"/>
+ <pt x="-137" y="-66" on="1"/>
+ <pt x="-137" y="187" on="1"/>
+ <pt x="-173" y="187" on="1"/>
+ <pt x="-173" y="420" on="1"/>
+ <pt x="-137" y="420" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="D" xMin="-137" yMin="-66" xMax="1099" yMax="646">
+ <contour>
+ <pt x="-137" y="646" on="1"/>
+ <pt x="140" y="646" on="1"/>
+ <pt x="140" y="420" on="1"/>
+ <pt x="396" y="420" on="1"/>
+ <pt x="396" y="584" on="1"/>
+ <pt x="748" y="584" on="1"/>
+ <pt x="748" y="420" on="1"/>
+ <pt x="1099" y="420" on="1"/>
+ <pt x="1099" y="187" on="1"/>
+ <pt x="748" y="187" on="1"/>
+ <pt x="748" y="-9" on="1"/>
+ <pt x="396" y="-9" on="1"/>
+ <pt x="396" y="187" on="1"/>
+ <pt x="140" y="187" on="1"/>
+ <pt x="140" y="-66" on="1"/>
+ <pt x="-137" y="-66" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="E" xMin="-127" yMin="-100" xMax="839" yMax="663">
+ <contour>
+ <pt x="121" y="650" on="1"/>
+ <pt x="398" y="650" on="1"/>
+ <pt x="398" y="592" on="1"/>
+ <pt x="596" y="663" on="1"/>
+ <pt x="839" y="-6" on="1"/>
+ <pt x="579" y="-100" on="1"/>
+ <pt x="398" y="396" on="1"/>
+ <pt x="398" y="-62" on="1"/>
+ <pt x="150" y="-62" on="1"/>
+ <pt x="150" y="-66" on="1"/>
+ <pt x="-127" y="-66" on="1"/>
+ <pt x="-127" y="646" on="1"/>
+ <pt x="121" y="646" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="F" xMin="-147" yMin="-115" xMax="872" yMax="650">
+ <contour>
+ <pt x="101" y="650" on="1"/>
+ <pt x="378" y="650" on="1"/>
+ <pt x="378" y="561" on="1"/>
+ <pt x="516" y="640" on="1"/>
+ <pt x="872" y="24" on="1"/>
+ <pt x="632" y="-115" on="1"/>
+ <pt x="378" y="325" on="1"/>
+ <pt x="378" y="-62" on="1"/>
+ <pt x="130" y="-62" on="1"/>
+ <pt x="130" y="-66" on="1"/>
+ <pt x="-147" y="-66" on="1"/>
+ <pt x="-147" y="646" on="1"/>
+ <pt x="101" y="646" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="space"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Weight
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Remove Overlaps Test
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.000;NONE;RemoveOverlapsTest-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Remove Overlaps Test Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ RemoveOverlapsTest-Regular
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-100"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ </extraNames>
+ </post>
+
+ <STAT>
+ <Version value="0x00010001"/>
+ <DesignAxisRecordSize value="8"/>
+ <!-- DesignAxisCount=1 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="256"/> <!-- Weight -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=0 -->
+ <ElidedFallbackNameID value="2"/> <!-- Regular -->
+ </STAT>
+
+</ttFont>
diff --git a/Tests/varLib/instancer_test.py b/Tests/varLib/instancer/instancer_test.py
index 7c587b59..cb7e8547 100644
--- a/Tests/varLib/instancer_test.py
+++ b/Tests/varLib/instancer/instancer_test.py
@@ -1,5 +1,5 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag
+from fontTools.misc.fixedTools import floatToFixedToFloat
from fontTools import ttLib
from fontTools import designspaceLib
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
@@ -14,20 +14,17 @@ from fontTools.varLib import featureVars
from fontTools.varLib import models
import collections
from copy import deepcopy
+from io import BytesIO, StringIO
import logging
import os
import re
+from types import SimpleNamespace
import pytest
-TESTDATA = os.path.join(os.path.dirname(__file__), "data")
-
+# see Tests/varLib/instancer/conftest.py for "varfont" fixture definition
-@pytest.fixture
-def varfont():
- f = ttLib.TTFont()
- f.importXML(os.path.join(TESTDATA, "PartialInstancerTest-VF.ttx"))
- return f
+TESTDATA = os.path.join(os.path.dirname(__file__), "data")
@pytest.fixture(params=[True, False], ids=["optimize", "no-optimize"])
@@ -144,7 +141,7 @@ class InstantiateGvarTest(object):
assert "gvar" not in varfont
def test_composite_glyph_not_in_gvar(self, varfont):
- """ The 'minus' glyph is a composite glyph, which references 'hyphen' as a
+ """The 'minus' glyph is a composite glyph, which references 'hyphen' as a
component, but has no tuple variations in gvar table, so the component offset
and the phantom points do not change; however the sidebearings and bounding box
do change as a result of the parent glyph 'hyphen' changing.
@@ -337,12 +334,12 @@ class InstantiateHVARTest(object):
{"wdth": -1.0},
[
{"wght": (-1.0, -1.0, 0.0)},
- {"wght": (0.0, 0.61, 1.0)},
- {"wght": (0.61, 1.0, 1.0)},
+ {"wght": (0.0, 0.6099854, 1.0)},
+ {"wght": (0.6099854, 1.0, 1.0)},
],
[-11, 31, 51],
),
- ({"wdth": 0}, [{"wght": (0.61, 1.0, 1.0)}], [-4]),
+ ({"wdth": 0}, [{"wght": (0.6099854, 1.0, 1.0)}], [-4]),
],
)
def test_partial_instance(self, varfont, location, expectedRegions, expectedDeltas):
@@ -354,7 +351,12 @@ class InstantiateHVARTest(object):
regions = varStore.VarRegionList.Region
fvarAxes = [a for a in varfont["fvar"].axes if a.axisTag not in location]
- assert [reg.get_support(fvarAxes) for reg in regions] == expectedRegions
+ regionDicts = [reg.get_support(fvarAxes) for reg in regions]
+ assert len(regionDicts) == len(expectedRegions)
+ for region, expectedRegion in zip(regionDicts, expectedRegions):
+ assert region.keys() == expectedRegion.keys()
+ for axisTag, support in region.items():
+ assert support == pytest.approx(expectedRegion[axisTag])
assert len(varStore.VarData) == 1
assert varStore.VarData[0].ItemCount == 2
@@ -377,6 +379,26 @@ class InstantiateHVARTest(object):
assert "HVAR" not in varfont
+ def test_partial_instance_keep_empty_table(self, varfont):
+ # Append an additional dummy axis to fvar, for which the current HVAR table
+ # in our test 'varfont' contains no variation data.
+ # Instancing the other two wght and wdth axes should leave HVAR table empty,
+ # to signal there are variations to the glyph's advance widths.
+ fvar = varfont["fvar"]
+ axis = _f_v_a_r.Axis()
+ axis.axisTag = "TEST"
+ fvar.axes.append(axis)
+
+ instancer.instantiateHVAR(varfont, {"wght": 0, "wdth": 0})
+
+ assert "HVAR" in varfont
+
+ varStore = varfont["HVAR"].table.VarStore
+
+ assert varStore.VarRegionList.RegionCount == 0
+ assert not varStore.VarRegionList.Region
+ assert varStore.VarRegionList.RegionAxisCount == 1
+
class InstantiateItemVariationStoreTest(object):
def test_VarRegion_get_support(self):
@@ -489,33 +511,40 @@ class TupleVarStoreAdapterTest(object):
[TupleVariation({"wdth": (-1.0, -1.0, 0)}, [-12, 8])],
]
- def test_dropAxes(self):
+ def test_rebuildRegions(self):
regions = [
{"wght": (-1.0, -1.0, 0)},
{"wght": (0.0, 1.0, 1.0)},
{"wdth": (-1.0, -1.0, 0)},
- {"opsz": (0.0, 1.0, 1.0)},
{"wght": (-1.0, -1.0, 0), "wdth": (-1.0, -1.0, 0)},
- {"wght": (0, 0.5, 1.0), "wdth": (-1.0, -1.0, 0)},
- {"wght": (0.5, 1.0, 1.0), "wdth": (-1.0, -1.0, 0)},
+ {"wght": (0, 1.0, 1.0), "wdth": (-1.0, -1.0, 0)},
]
- axisOrder = ["wght", "wdth", "opsz"]
- adapter = instancer._TupleVarStoreAdapter(regions, axisOrder, [], itemCounts=[])
+ axisOrder = ["wght", "wdth"]
+ variations = []
+ for region in regions:
+ variations.append(TupleVariation(region, [100]))
+ tupleVarData = [variations[:3], variations[3:]]
+ adapter = instancer._TupleVarStoreAdapter(
+ regions, axisOrder, tupleVarData, itemCounts=[1, 1]
+ )
+
+ adapter.rebuildRegions()
+
+ assert adapter.regions == regions
+
+ del tupleVarData[0][2]
+ tupleVarData[1][0].axes = {"wght": (-1.0, -0.5, 0)}
+ tupleVarData[1][1].axes = {"wght": (0, 0.5, 1.0)}
- adapter.dropAxes({"wdth"})
+ adapter.rebuildRegions()
assert adapter.regions == [
{"wght": (-1.0, -1.0, 0)},
{"wght": (0.0, 1.0, 1.0)},
- {"opsz": (0.0, 1.0, 1.0)},
- {"wght": (0.0, 0.5, 1.0)},
- {"wght": (0.5, 1.0, 1.0)},
+ {"wght": (-1.0, -0.5, 0)},
+ {"wght": (0, 0.5, 1.0)},
]
- adapter.dropAxes({"wght", "opsz"})
-
- assert adapter.regions == []
-
def test_roundtrip(self, fvarAxes):
regions = [
{"wght": (-1.0, -1.0, 0)},
@@ -920,6 +949,208 @@ class InstantiateAvarTest(object):
assert "avar" not in varfont
+ @staticmethod
+ def quantizeF2Dot14Floats(mapping):
+ return {
+ floatToFixedToFloat(k, 14): floatToFixedToFloat(v, 14)
+ for k, v in mapping.items()
+ }
+
+ # the following values come from NotoSans-VF.ttf
+ DFLT_WGHT_MAPPING = {
+ -1.0: -1.0,
+ -0.6667: -0.7969,
+ -0.3333: -0.5,
+ 0: 0,
+ 0.2: 0.18,
+ 0.4: 0.38,
+ 0.6: 0.61,
+ 0.8: 0.79,
+ 1.0: 1.0,
+ }
+
+ DFLT_WDTH_MAPPING = {-1.0: -1.0, -0.6667: -0.7, -0.3333: -0.36664, 0: 0, 1.0: 1.0}
+
+ @pytest.fixture
+ def varfont(self):
+ fvarAxes = ("wght", (100, 400, 900)), ("wdth", (62.5, 100, 100))
+ avarSegments = {
+ "wght": self.quantizeF2Dot14Floats(self.DFLT_WGHT_MAPPING),
+ "wdth": self.quantizeF2Dot14Floats(self.DFLT_WDTH_MAPPING),
+ }
+ varfont = ttLib.TTFont()
+ varfont["name"] = ttLib.newTable("name")
+ varLib._add_fvar(varfont, _makeDSAxesDict(fvarAxes), instances=())
+ avar = varfont["avar"] = ttLib.newTable("avar")
+ avar.segments = avarSegments
+ return varfont
+
+ @pytest.mark.parametrize(
+ "axisLimits, expectedSegments",
+ [
+ pytest.param(
+ {"wght": (100, 900)},
+ {"wght": DFLT_WGHT_MAPPING, "wdth": DFLT_WDTH_MAPPING},
+ id="wght=100:900",
+ ),
+ pytest.param(
+ {"wght": (400, 900)},
+ {
+ "wght": {
+ -1.0: -1.0,
+ 0: 0,
+ 0.2: 0.18,
+ 0.4: 0.38,
+ 0.6: 0.61,
+ 0.8: 0.79,
+ 1.0: 1.0,
+ },
+ "wdth": DFLT_WDTH_MAPPING,
+ },
+ id="wght=400:900",
+ ),
+ pytest.param(
+ {"wght": (100, 400)},
+ {
+ "wght": {
+ -1.0: -1.0,
+ -0.6667: -0.7969,
+ -0.3333: -0.5,
+ 0: 0,
+ 1.0: 1.0,
+ },
+ "wdth": DFLT_WDTH_MAPPING,
+ },
+ id="wght=100:400",
+ ),
+ pytest.param(
+ {"wght": (400, 800)},
+ {
+ "wght": {
+ -1.0: -1.0,
+ 0: 0,
+ 0.25: 0.22784,
+ 0.50006: 0.48103,
+ 0.75: 0.77214,
+ 1.0: 1.0,
+ },
+ "wdth": DFLT_WDTH_MAPPING,
+ },
+ id="wght=400:800",
+ ),
+ pytest.param(
+ {"wght": (400, 700)},
+ {
+ "wght": {
+ -1.0: -1.0,
+ 0: 0,
+ 0.3334: 0.2951,
+ 0.66675: 0.623,
+ 1.0: 1.0,
+ },
+ "wdth": DFLT_WDTH_MAPPING,
+ },
+ id="wght=400:700",
+ ),
+ pytest.param(
+ {"wght": (400, 600)},
+ {
+ "wght": {-1.0: -1.0, 0: 0, 0.5: 0.47363, 1.0: 1.0},
+ "wdth": DFLT_WDTH_MAPPING,
+ },
+ id="wght=400:600",
+ ),
+ pytest.param(
+ {"wdth": (62.5, 100)},
+ {
+ "wght": DFLT_WGHT_MAPPING,
+ "wdth": {
+ -1.0: -1.0,
+ -0.6667: -0.7,
+ -0.3333: -0.36664,
+ 0: 0,
+ 1.0: 1.0,
+ },
+ },
+ id="wdth=62.5:100",
+ ),
+ pytest.param(
+ {"wdth": (70, 100)},
+ {
+ "wght": DFLT_WGHT_MAPPING,
+ "wdth": {
+ -1.0: -1.0,
+ -0.8334: -0.85364,
+ -0.4166: -0.44714,
+ 0: 0,
+ 1.0: 1.0,
+ },
+ },
+ id="wdth=70:100",
+ ),
+ pytest.param(
+ {"wdth": (75, 100)},
+ {
+ "wght": DFLT_WGHT_MAPPING,
+ "wdth": {-1.0: -1.0, -0.49994: -0.52374, 0: 0, 1.0: 1.0},
+ },
+ id="wdth=75:100",
+ ),
+ pytest.param(
+ {"wdth": (77, 100)},
+ {
+ "wght": DFLT_WGHT_MAPPING,
+ "wdth": {-1.0: -1.0, -0.54346: -0.56696, 0: 0, 1.0: 1.0},
+ },
+ id="wdth=77:100",
+ ),
+ pytest.param(
+ {"wdth": (87.5, 100)},
+ {"wght": DFLT_WGHT_MAPPING, "wdth": {-1.0: -1.0, 0: 0, 1.0: 1.0}},
+ id="wdth=87.5:100",
+ ),
+ ],
+ )
+ def test_limit_axes(self, varfont, axisLimits, expectedSegments):
+ instancer.instantiateAvar(varfont, axisLimits)
+
+ newSegments = varfont["avar"].segments
+ expectedSegments = {
+ axisTag: self.quantizeF2Dot14Floats(mapping)
+ for axisTag, mapping in expectedSegments.items()
+ }
+ assert newSegments == expectedSegments
+
+ @pytest.mark.parametrize(
+ "invalidSegmentMap",
+ [
+ pytest.param({0.5: 0.5}, id="missing-required-maps-1"),
+ pytest.param({-1.0: -1.0, 1.0: 1.0}, id="missing-required-maps-2"),
+ pytest.param(
+ {-1.0: -1.0, 0: 0, 0.5: 0.5, 0.6: 0.4, 1.0: 1.0},
+ id="retrograde-value-maps",
+ ),
+ ],
+ )
+ def test_drop_invalid_segment_map(self, varfont, invalidSegmentMap, caplog):
+ varfont["avar"].segments["wght"] = invalidSegmentMap
+
+ with caplog.at_level(logging.WARNING, logger="fontTools.varLib.instancer"):
+ instancer.instantiateAvar(varfont, {"wght": (100, 400)})
+
+ assert "Invalid avar" in caplog.text
+ assert "wght" not in varfont["avar"].segments
+
+ def test_isValidAvarSegmentMap(self):
+ assert instancer._isValidAvarSegmentMap("FOOO", {})
+ assert instancer._isValidAvarSegmentMap("FOOO", {-1.0: -1.0, 0: 0, 1.0: 1.0})
+ assert instancer._isValidAvarSegmentMap(
+ "FOOO", {-1.0: -1.0, 0: 0, 0.5: 0.5, 1.0: 1.0}
+ )
+ assert instancer._isValidAvarSegmentMap(
+ "FOOO", {-1.0: -1.0, 0: 0, 0.5: 0.5, 0.7: 0.5, 1.0: 1.0}
+ )
+
class InstantiateFvarTest(object):
@pytest.mark.parametrize(
@@ -975,8 +1206,8 @@ class InstantiateSTATTest(object):
@pytest.mark.parametrize(
"location, expected",
[
- ({"wght": 400}, ["Condensed", "Upright"]),
- ({"wdth": 100}, ["Thin", "Regular", "Black", "Upright"]),
+ ({"wght": 400}, ["Regular", "Condensed", "Upright", "Normal"]),
+ ({"wdth": 100}, ["Thin", "Regular", "Black", "Upright", "Normal"]),
],
)
def test_pin_and_drop_axis(self, varfont, location, expected):
@@ -985,7 +1216,7 @@ class InstantiateSTATTest(object):
stat = varfont["STAT"].table
designAxes = {a.AxisTag for a in stat.DesignAxisRecord.Axis}
- assert designAxes == {"wght", "wdth", "ital"}.difference(location)
+ assert designAxes == {"wght", "wdth", "ital"}
name = varfont["name"]
valueNames = []
@@ -995,7 +1226,23 @@ class InstantiateSTATTest(object):
assert valueNames == expected
- def test_skip_empty_table(self, varfont):
+ def test_skip_table_no_axis_value_array(self, varfont):
+ varfont["STAT"].table.AxisValueArray = None
+
+ instancer.instantiateSTAT(varfont, {"wght": 100})
+
+ assert len(varfont["STAT"].table.DesignAxisRecord.Axis) == 3
+ assert varfont["STAT"].table.AxisValueArray is None
+
+ def test_skip_table_axis_value_array_empty(self, varfont):
+ varfont["STAT"].table.AxisValueArray.AxisValue = []
+
+ instancer.instantiateSTAT(varfont, {"wght": 100})
+
+ assert len(varfont["STAT"].table.DesignAxisRecord.Axis) == 3
+ assert not varfont["STAT"].table.AxisValueArray.AxisValue
+
+ def test_skip_table_no_design_axes(self, varfont):
stat = otTables.STAT()
stat.Version = 0x00010001
stat.populateDefaults()
@@ -1007,45 +1254,88 @@ class InstantiateSTATTest(object):
assert not varfont["STAT"].table.DesignAxisRecord
- def test_drop_table(self, varfont):
- stat = otTables.STAT()
- stat.Version = 0x00010001
- stat.populateDefaults()
- stat.DesignAxisRecord = otTables.AxisRecordArray()
- axis = otTables.AxisRecord()
- axis.AxisTag = "wght"
- axis.AxisNameID = 0
- axis.AxisOrdering = 0
- stat.DesignAxisRecord.Axis = [axis]
- varfont["STAT"].table = stat
-
- instancer.instantiateSTAT(varfont, {"wght": 100})
+ @staticmethod
+ def get_STAT_axis_values(stat):
+ axes = stat.DesignAxisRecord.Axis
+ result = []
+ for axisValue in stat.AxisValueArray.AxisValue:
+ if axisValue.Format == 1:
+ result.append((axes[axisValue.AxisIndex].AxisTag, axisValue.Value))
+ elif axisValue.Format == 3:
+ result.append(
+ (
+ axes[axisValue.AxisIndex].AxisTag,
+ (axisValue.Value, axisValue.LinkedValue),
+ )
+ )
+ elif axisValue.Format == 2:
+ result.append(
+ (
+ axes[axisValue.AxisIndex].AxisTag,
+ (
+ axisValue.RangeMinValue,
+ axisValue.NominalValue,
+ axisValue.RangeMaxValue,
+ ),
+ )
+ )
+ elif axisValue.Format == 4:
+ result.append(
+ tuple(
+ (axes[rec.AxisIndex].AxisTag, rec.Value)
+ for rec in axisValue.AxisValueRecord
+ )
+ )
+ else:
+ raise AssertionError(axisValue.Format)
+ return result
+
+ def test_limit_axes(self, varfont2):
+ instancer.instantiateSTAT(varfont2, {"wght": (400, 500), "wdth": (75, 100)})
+
+ assert len(varfont2["STAT"].table.AxisValueArray.AxisValue) == 5
+ assert self.get_STAT_axis_values(varfont2["STAT"].table) == [
+ ("wght", (400.0, 700.0)),
+ ("wght", 500.0),
+ ("wdth", (93.75, 100.0, 100.0)),
+ ("wdth", (81.25, 87.5, 93.75)),
+ ("wdth", (68.75, 75.0, 81.25)),
+ ]
- assert "STAT" not in varfont
+ def test_limit_axis_value_format_4(self, varfont2):
+ stat = varfont2["STAT"].table
+ axisValue = otTables.AxisValue()
+ axisValue.Format = 4
+ axisValue.AxisValueRecord = []
+ for tag, value in (("wght", 575), ("wdth", 90)):
+ rec = otTables.AxisValueRecord()
+ rec.AxisIndex = next(
+ i for i, a in enumerate(stat.DesignAxisRecord.Axis) if a.AxisTag == tag
+ )
+ rec.Value = value
+ axisValue.AxisValueRecord.append(rec)
+ stat.AxisValueArray.AxisValue.append(axisValue)
-def test_pruningUnusedNames(varfont):
- varNameIDs = instancer.getVariationNameIDs(varfont)
+ instancer.instantiateSTAT(varfont2, {"wght": (100, 600)})
- assert varNameIDs == set(range(256, 296 + 1))
+ assert axisValue in varfont2["STAT"].table.AxisValueArray.AxisValue
- fvar = varfont["fvar"]
- stat = varfont["STAT"].table
+ instancer.instantiateSTAT(varfont2, {"wdth": (62.5, 87.5)})
- with instancer.pruningUnusedNames(varfont):
- del fvar.axes[0] # Weight (nameID=256)
- del fvar.instances[0] # Thin (nameID=258)
- del stat.DesignAxisRecord.Axis[0] # Weight (nameID=256)
- del stat.AxisValueArray.AxisValue[0] # Thin (nameID=258)
+ assert axisValue not in varfont2["STAT"].table.AxisValueArray.AxisValue
- assert not any(n for n in varfont["name"].names if n.nameID in {256, 258})
+ def test_unknown_axis_value_format(self, varfont2, caplog):
+ stat = varfont2["STAT"].table
+ axisValue = otTables.AxisValue()
+ axisValue.Format = 5
+ stat.AxisValueArray.AxisValue.append(axisValue)
- with instancer.pruningUnusedNames(varfont):
- del varfont["fvar"]
- del varfont["STAT"]
+ with caplog.at_level(logging.WARNING, logger="fontTools.varLib.instancer"):
+ instancer.instantiateSTAT(varfont2, {"wght": 400})
- assert not any(n for n in varfont["name"].names if n.nameID in varNameIDs)
- assert "ltag" not in varfont
+ assert "Unknown AxisValue table format (5)" in caplog.text
+ assert axisValue in varfont2["STAT"].table.AxisValueArray.AxisValue
def test_setMacOverlapFlags():
@@ -1083,6 +1373,13 @@ def varfont2():
return f
+@pytest.fixture
+def varfont3():
+ f = ttLib.TTFont(recalcTimestamp=False)
+ f.importXML(os.path.join(TESTDATA, "PartialInstancerTest3-VF.ttx"))
+ return f
+
+
def _dump_ttx(ttFont):
# compile to temporary bytes stream, reload and dump to XML
tmp = BytesIO()
@@ -1094,13 +1391,16 @@ def _dump_ttx(ttFont):
return _strip_ttLibVersion(s.getvalue())
-def _get_expected_instance_ttx(wght, wdth):
+def _get_expected_instance_ttx(
+ name, *locations, overlap=instancer.OverlapMode.KEEP_AND_SET_FLAGS
+):
+ filename = f"{name}-VF-instance-{','.join(str(loc) for loc in locations)}"
+ if overlap == instancer.OverlapMode.KEEP_AND_DONT_SET_FLAGS:
+ filename += "-no-overlap-flags"
+ elif overlap == instancer.OverlapMode.REMOVE:
+ filename += "-no-overlaps"
with open(
- os.path.join(
- TESTDATA,
- "test_results",
- "PartialInstancerTest2-VF-instance-{0},{1}.ttx".format(wght, wdth),
- ),
+ os.path.join(TESTDATA, "test_results", f"{filename}.ttx"),
"r",
encoding="utf-8",
) as fp:
@@ -1116,7 +1416,7 @@ class InstantiateVariableFontTest(object):
partial = instancer.instantiateVariableFont(varfont2, {"wght": wght})
instance = instancer.instantiateVariableFont(partial, {"wdth": wdth})
- expected = _get_expected_instance_ttx(wght, wdth)
+ expected = _get_expected_instance_ttx("PartialInstancerTest2", wght, wdth)
assert _dump_ttx(instance) == expected
@@ -1125,7 +1425,30 @@ class InstantiateVariableFontTest(object):
varfont2, {"wght": None, "wdth": None}
)
- expected = _get_expected_instance_ttx(400, 100)
+ expected = _get_expected_instance_ttx("PartialInstancerTest2", 400, 100)
+
+ assert _dump_ttx(instance) == expected
+
+ @pytest.mark.parametrize(
+ "overlap, wght",
+ [
+ (instancer.OverlapMode.KEEP_AND_DONT_SET_FLAGS, 400),
+ (instancer.OverlapMode.REMOVE, 400),
+ (instancer.OverlapMode.REMOVE, 700),
+ ],
+ )
+ def test_overlap(self, varfont3, wght, overlap):
+ pytest.importorskip("pathops")
+
+ location = {"wght": wght}
+
+ instance = instancer.instantiateVariableFont(
+ varfont3, location, overlap=overlap
+ )
+
+ expected = _get_expected_instance_ttx(
+ "PartialInstancerTest3", wght, overlap=overlap
+ )
assert _dump_ttx(instance) == expected
@@ -1316,13 +1639,218 @@ class InstantiateFeatureVariationsTest(object):
assert len(rec1.ConditionSet.ConditionTable) == 2
assert rec1.ConditionSet.ConditionTable[0].Format == 2
+ def test_GSUB_FeatureVariations_is_None(self, varfont2):
+ varfont2["GSUB"].table.Version = 0x00010001
+ varfont2["GSUB"].table.FeatureVariations = None
+ tmp = BytesIO()
+ varfont2.save(tmp)
+ varfont = ttLib.TTFont(tmp)
+
+ # DO NOT raise an exception when the optional 'FeatureVariations' attribute is
+ # present but is set to None (e.g. with GSUB 1.1); skip and do nothing.
+ assert varfont["GSUB"].table.FeatureVariations is None
+ instancer.instantiateFeatureVariations(varfont, {"wght": 400, "wdth": 100})
+ assert varfont["GSUB"].table.FeatureVariations is None
+
+
+class LimitTupleVariationAxisRangesTest:
+ def check_limit_single_var_axis_range(self, var, axisTag, axisRange, expected):
+ result = instancer.limitTupleVariationAxisRange(var, axisTag, axisRange)
+ print(result)
+
+ assert len(result) == len(expected)
+ for v1, v2 in zip(result, expected):
+ assert v1.coordinates == pytest.approx(v2.coordinates)
+ assert v1.axes.keys() == v2.axes.keys()
+ for k in v1.axes:
+ p, q = v1.axes[k], v2.axes[k]
+ assert p == pytest.approx(q)
+
+ @pytest.mark.parametrize(
+ "var, axisTag, newMax, expected",
+ [
+ (
+ TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100]),
+ "wdth",
+ 0.5,
+ [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100])],
+ ),
+ (
+ TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100]),
+ "wght",
+ 0.5,
+ [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [50, 50])],
+ ),
+ (
+ TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100]),
+ "wght",
+ 0.8,
+ [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [80, 80])],
+ ),
+ (
+ TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100]),
+ "wght",
+ 1.0,
+ [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100])],
+ ),
+ (TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100]), "wght", 0.0, []),
+ (TupleVariation({"wght": (0.5, 1.0, 1.0)}, [100, 100]), "wght", 0.4, []),
+ (
+ TupleVariation({"wght": (0.0, 0.5, 1.0)}, [100, 100]),
+ "wght",
+ 0.5,
+ [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [100, 100])],
+ ),
+ (
+ TupleVariation({"wght": (0.0, 0.5, 1.0)}, [100, 100]),
+ "wght",
+ 0.4,
+ [TupleVariation({"wght": (0.0, 1.0, 1.0)}, [80, 80])],
+ ),
+ (
+ TupleVariation({"wght": (0.0, 0.5, 1.0)}, [100, 100]),
+ "wght",
+ 0.6,
+ [TupleVariation({"wght": (0.0, 0.833334, 1.666667)}, [100, 100])],
+ ),
+ (
+ TupleVariation({"wght": (0.0, 0.2, 1.0)}, [100, 100]),
+ "wght",
+ 0.4,
+ [
+ TupleVariation({"wght": (0.0, 0.5, 1.99994)}, [100, 100]),
+ TupleVariation({"wght": (0.5, 1.0, 1.0)}, [8.33333, 8.33333]),
+ ],
+ ),
+ (
+ TupleVariation({"wght": (0.0, 0.2, 1.0)}, [100, 100]),
+ "wght",
+ 0.5,
+ [TupleVariation({"wght": (0.0, 0.4, 1.99994)}, [100, 100])],
+ ),
+ (
+ TupleVariation({"wght": (0.5, 0.5, 1.0)}, [100, 100]),
+ "wght",
+ 0.5,
+ [TupleVariation({"wght": (1.0, 1.0, 1.0)}, [100, 100])],
+ ),
+ ],
+ )
+ def test_positive_var(self, var, axisTag, newMax, expected):
+ axisRange = instancer.NormalizedAxisRange(0, newMax)
+ self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected)
+
+ @pytest.mark.parametrize(
+ "var, axisTag, newMin, expected",
+ [
+ (
+ TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100]),
+ "wdth",
+ -0.5,
+ [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100])],
+ ),
+ (
+ TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100]),
+ "wght",
+ -0.5,
+ [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [50, 50])],
+ ),
+ (
+ TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100]),
+ "wght",
+ -0.8,
+ [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [80, 80])],
+ ),
+ (
+ TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100]),
+ "wght",
+ -1.0,
+ [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100])],
+ ),
+ (TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100]), "wght", 0.0, []),
+ (
+ TupleVariation({"wght": (-1.0, -1.0, -0.5)}, [100, 100]),
+ "wght",
+ -0.4,
+ [],
+ ),
+ (
+ TupleVariation({"wght": (-1.0, -0.5, 0.0)}, [100, 100]),
+ "wght",
+ -0.5,
+ [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [100, 100])],
+ ),
+ (
+ TupleVariation({"wght": (-1.0, -0.5, 0.0)}, [100, 100]),
+ "wght",
+ -0.4,
+ [TupleVariation({"wght": (-1.0, -1.0, 0.0)}, [80, 80])],
+ ),
+ (
+ TupleVariation({"wght": (-1.0, -0.5, 0.0)}, [100, 100]),
+ "wght",
+ -0.6,
+ [TupleVariation({"wght": (-1.666667, -0.833334, 0.0)}, [100, 100])],
+ ),
+ (
+ TupleVariation({"wght": (-1.0, -0.2, 0.0)}, [100, 100]),
+ "wght",
+ -0.4,
+ [
+ TupleVariation({"wght": (-2.0, -0.5, -0.0)}, [100, 100]),
+ TupleVariation({"wght": (-1.0, -1.0, -0.5)}, [8.33333, 8.33333]),
+ ],
+ ),
+ (
+ TupleVariation({"wght": (-1.0, -0.2, 0.0)}, [100, 100]),
+ "wght",
+ -0.5,
+ [TupleVariation({"wght": (-2.0, -0.4, 0.0)}, [100, 100])],
+ ),
+ (
+ TupleVariation({"wght": (-1.0, -0.5, -0.5)}, [100, 100]),
+ "wght",
+ -0.5,
+ [TupleVariation({"wght": (-1.0, -1.0, -1.0)}, [100, 100])],
+ ),
+ ],
+ )
+ def test_negative_var(self, var, axisTag, newMin, expected):
+ axisRange = instancer.NormalizedAxisRange(newMin, 0)
+ self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected)
+
+
+@pytest.mark.parametrize(
+ "oldRange, newRange, expected",
+ [
+ ((1.0, -1.0), (-1.0, 1.0), None), # invalid oldRange min > max
+ ((0.6, 1.0), (0, 0.5), None),
+ ((-1.0, -0.6), (-0.5, 0), None),
+ ((0.4, 1.0), (0, 0.5), (0.8, 1.0)),
+ ((-1.0, -0.4), (-0.5, 0), (-1.0, -0.8)),
+ ((0.4, 1.0), (0, 0.4), (1.0, 1.0)),
+ ((-1.0, -0.4), (-0.4, 0), (-1.0, -1.0)),
+ ((-0.5, 0.5), (-0.4, 0.4), (-1.0, 1.0)),
+ ((0, 1.0), (-1.0, 0), (0, 0)), # or None?
+ ((-1.0, 0), (0, 1.0), (0, 0)), # or None?
+ ],
+)
+def test_limitFeatureVariationConditionRange(oldRange, newRange, expected):
+ condition = featureVars.buildConditionTable(0, *oldRange)
+
+ result = instancer._limitFeatureVariationConditionRange(
+ condition, instancer.NormalizedAxisRange(*newRange)
+ )
+
+ assert result == expected
+
@pytest.mark.parametrize(
"limits, expected",
[
(["wght=400", "wdth=100"], {"wght": 400, "wdth": 100}),
(["wght=400:900"], {"wght": (400, 900)}),
- (["slnt=11.4"], {"slnt": 11.4}),
+ (["slnt=11.4"], {"slnt": pytest.approx(11.399994)}),
(["ABCD=drop"], {"ABCD": None}),
],
)
@@ -1343,12 +1871,17 @@ def test_normalizeAxisLimits_tuple(varfont):
assert normalized == {"wght": (-1.0, 0)}
+def test_normalizeAxisLimits_unsupported_range(varfont):
+ with pytest.raises(NotImplementedError, match="Unsupported range"):
+ instancer.normalizeAxisLimits(varfont, {"wght": (401, 700)})
+
+
def test_normalizeAxisLimits_no_avar(varfont):
del varfont["avar"]
- normalized = instancer.normalizeAxisLimits(varfont, {"wght": (500, 600)})
+ normalized = instancer.normalizeAxisLimits(varfont, {"wght": (400, 500)})
- assert normalized["wght"] == pytest.approx((0.2, 0.4), 1e-4)
+ assert normalized["wght"] == pytest.approx((0, 0.2), 1e-4)
def test_normalizeAxisLimits_missing_from_fvar(varfont):
diff --git a/Tests/varLib/instancer/names_test.py b/Tests/varLib/instancer/names_test.py
new file mode 100644
index 00000000..9774458a
--- /dev/null
+++ b/Tests/varLib/instancer/names_test.py
@@ -0,0 +1,322 @@
+from fontTools.ttLib.tables import otTables
+from fontTools.otlLib.builder import buildStatTable
+from fontTools.varLib import instancer
+
+import pytest
+
+
+def test_pruningUnusedNames(varfont):
+ varNameIDs = instancer.names.getVariationNameIDs(varfont)
+
+ assert varNameIDs == set(range(256, 297 + 1))
+
+ fvar = varfont["fvar"]
+ stat = varfont["STAT"].table
+
+ with instancer.names.pruningUnusedNames(varfont):
+ del fvar.axes[0] # Weight (nameID=256)
+ del fvar.instances[0] # Thin (nameID=258)
+ del stat.DesignAxisRecord.Axis[0] # Weight (nameID=256)
+ del stat.AxisValueArray.AxisValue[0] # Thin (nameID=258)
+
+ assert not any(n for n in varfont["name"].names if n.nameID in {256, 258})
+
+ with instancer.names.pruningUnusedNames(varfont):
+ del varfont["fvar"]
+ del varfont["STAT"]
+
+ assert not any(n for n in varfont["name"].names if n.nameID in varNameIDs)
+ assert "ltag" not in varfont
+
+
+def _test_name_records(varfont, expected, isNonRIBBI, platforms=[0x409]):
+ nametable = varfont["name"]
+ font_names = {
+ (r.nameID, r.platformID, r.platEncID, r.langID): r.toUnicode()
+ for r in nametable.names
+ }
+ for k in expected:
+ if k[-1] not in platforms:
+ continue
+ assert font_names[k] == expected[k]
+
+ font_nameids = set(i[0] for i in font_names)
+ if isNonRIBBI:
+ assert 16 in font_nameids
+ assert 17 in font_nameids
+
+ if "fvar" not in varfont:
+ assert 25 not in font_nameids
+
+
+@pytest.mark.parametrize(
+ "limits, expected, isNonRIBBI",
+ [
+ # Regular
+ (
+ {"wght": 400},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font",
+ (2, 3, 1, 0x409): "Regular",
+ (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Regular",
+ (6, 3, 1, 0x409): "TestVariableFont-Regular",
+ },
+ False,
+ ),
+ # Regular Normal (width axis Normal isn't included since it is elided)
+ (
+ {"wght": 400, "wdth": 100},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font",
+ (2, 3, 1, 0x409): "Regular",
+ (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Regular",
+ (6, 3, 1, 0x409): "TestVariableFont-Regular",
+ },
+ False,
+ ),
+ # Black
+ (
+ {"wght": 900},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font Black",
+ (2, 3, 1, 0x409): "Regular",
+ (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Black",
+ (6, 3, 1, 0x409): "TestVariableFont-Black",
+ (16, 3, 1, 0x409): "Test Variable Font",
+ (17, 3, 1, 0x409): "Black",
+ },
+ True,
+ ),
+ # Thin
+ (
+ {"wght": 100},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font Thin",
+ (2, 3, 1, 0x409): "Regular",
+ (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Thin",
+ (6, 3, 1, 0x409): "TestVariableFont-Thin",
+ (16, 3, 1, 0x409): "Test Variable Font",
+ (17, 3, 1, 0x409): "Thin",
+ },
+ True,
+ ),
+ # Thin Condensed
+ (
+ {"wght": 100, "wdth": 79},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font Thin Condensed",
+ (2, 3, 1, 0x409): "Regular",
+ (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-ThinCondensed",
+ (6, 3, 1, 0x409): "TestVariableFont-ThinCondensed",
+ (16, 3, 1, 0x409): "Test Variable Font",
+ (17, 3, 1, 0x409): "Thin Condensed",
+ },
+ True,
+ ),
+ # Condensed with unpinned weights
+ (
+ {"wdth": 79, "wght": instancer.AxisRange(400, 900)},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font Condensed",
+ (2, 3, 1, 0x409): "Regular",
+ (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Condensed",
+ (6, 3, 1, 0x409): "TestVariableFont-Condensed",
+ (16, 3, 1, 0x409): "Test Variable Font",
+ (17, 3, 1, 0x409): "Condensed",
+ },
+ True,
+ ),
+ ],
+)
+def test_updateNameTable_with_registered_axes_ribbi(
+ varfont, limits, expected, isNonRIBBI
+):
+ instancer.names.updateNameTable(varfont, limits)
+ _test_name_records(varfont, expected, isNonRIBBI)
+
+
+def test_updatetNameTable_axis_order(varfont):
+ axes = [
+ dict(
+ tag="wght",
+ name="Weight",
+ values=[
+ dict(value=400, name="Regular"),
+ ],
+ ),
+ dict(
+ tag="wdth",
+ name="Width",
+ values=[
+ dict(value=75, name="Condensed"),
+ ],
+ ),
+ ]
+ nametable = varfont["name"]
+ buildStatTable(varfont, axes)
+ instancer.names.updateNameTable(varfont, {"wdth": 75, "wght": 400})
+ assert nametable.getName(17, 3, 1, 0x409).toUnicode() == "Regular Condensed"
+
+ # Swap the axes so the names get swapped
+ axes[0], axes[1] = axes[1], axes[0]
+
+ buildStatTable(varfont, axes)
+ instancer.names.updateNameTable(varfont, {"wdth": 75, "wght": 400})
+ assert nametable.getName(17, 3, 1, 0x409).toUnicode() == "Condensed Regular"
+
+
+@pytest.mark.parametrize(
+ "limits, expected, isNonRIBBI",
+ [
+ # Regular | Normal
+ (
+ {"wght": 400},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font",
+ (2, 3, 1, 0x409): "Normal",
+ },
+ False,
+ ),
+ # Black | Negreta
+ (
+ {"wght": 900},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font Negreta",
+ (2, 3, 1, 0x409): "Normal",
+ (16, 3, 1, 0x409): "Test Variable Font",
+ (17, 3, 1, 0x409): "Negreta",
+ },
+ True,
+ ),
+ # Black Condensed | Negreta Zhuštěné
+ (
+ {"wght": 900, "wdth": 79},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font Negreta Zhuštěné",
+ (2, 3, 1, 0x409): "Normal",
+ (16, 3, 1, 0x409): "Test Variable Font",
+ (17, 3, 1, 0x409): "Negreta Zhuštěné",
+ },
+ True,
+ ),
+ ],
+)
+def test_updateNameTable_with_multilingual_names(varfont, limits, expected, isNonRIBBI):
+ name = varfont["name"]
+ # langID 0x405 is the Czech Windows langID
+ name.setName("Test Variable Font", 1, 3, 1, 0x405)
+ name.setName("Normal", 2, 3, 1, 0x405)
+ name.setName("Normal", 261, 3, 1, 0x405) # nameID 261=Regular STAT entry
+ name.setName("Negreta", 266, 3, 1, 0x405) # nameID 266=Black STAT entry
+ name.setName("Zhuštěné", 279, 3, 1, 0x405) # nameID 279=Condensed STAT entry
+
+ instancer.names.updateNameTable(varfont, limits)
+ _test_name_records(varfont, expected, isNonRIBBI, platforms=[0x405])
+
+
+def test_updateNameTable_missing_axisValues(varfont):
+ with pytest.raises(ValueError, match="Cannot find Axis Values \['wght=200'\]"):
+ instancer.names.updateNameTable(varfont, {"wght": 200})
+
+
+def test_updateNameTable_missing_stat(varfont):
+ del varfont["STAT"]
+ with pytest.raises(
+ ValueError, match="Cannot update name table since there is no STAT table."
+ ):
+ instancer.names.updateNameTable(varfont, {"wght": 400})
+
+
+@pytest.mark.parametrize(
+ "limits, expected, isNonRIBBI",
+ [
+ # Regular | Normal
+ (
+ {"wght": 400},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font",
+ (2, 3, 1, 0x409): "Italic",
+ (6, 3, 1, 0x409): "TestVariableFont-Italic",
+ },
+ False,
+ ),
+ # Black Condensed Italic
+ (
+ {"wght": 900, "wdth": 79},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font Black Condensed",
+ (2, 3, 1, 0x409): "Italic",
+ (6, 3, 1, 0x409): "TestVariableFont-BlackCondensedItalic",
+ (16, 3, 1, 0x409): "Test Variable Font",
+ (17, 3, 1, 0x409): "Black Condensed Italic",
+ },
+ True,
+ ),
+ ],
+)
+def test_updateNameTable_vf_with_italic_attribute(
+ varfont, limits, expected, isNonRIBBI
+):
+ font_link_axisValue = varfont["STAT"].table.AxisValueArray.AxisValue[4]
+ # Unset ELIDABLE_AXIS_VALUE_NAME flag
+ font_link_axisValue.Flags &= ~instancer.names.ELIDABLE_AXIS_VALUE_NAME
+ font_link_axisValue.ValueNameID = 294 # Roman --> Italic
+
+ instancer.names.updateNameTable(varfont, limits)
+ _test_name_records(varfont, expected, isNonRIBBI)
+
+
+def test_updateNameTable_format4_axisValues(varfont):
+ # format 4 axisValues should dominate the other axisValues
+ stat = varfont["STAT"].table
+
+ axisValue = otTables.AxisValue()
+ axisValue.Format = 4
+ axisValue.Flags = 0
+ varfont["name"].setName("Dominant Value", 297, 3, 1, 0x409)
+ axisValue.ValueNameID = 297
+ axisValue.AxisValueRecord = []
+ for tag, value in (("wght", 900), ("wdth", 79)):
+ rec = otTables.AxisValueRecord()
+ rec.AxisIndex = next(
+ i for i, a in enumerate(stat.DesignAxisRecord.Axis) if a.AxisTag == tag
+ )
+ rec.Value = value
+ axisValue.AxisValueRecord.append(rec)
+ stat.AxisValueArray.AxisValue.append(axisValue)
+
+ instancer.names.updateNameTable(varfont, {"wdth": 79, "wght": 900})
+ expected = {
+ (1, 3, 1, 0x409): "Test Variable Font Dominant Value",
+ (2, 3, 1, 0x409): "Regular",
+ (16, 3, 1, 0x409): "Test Variable Font",
+ (17, 3, 1, 0x409): "Dominant Value",
+ }
+ _test_name_records(varfont, expected, isNonRIBBI=True)
+
+
+def test_updateNameTable_elided_axisValues(varfont):
+ stat = varfont["STAT"].table
+ # set ELIDABLE_AXIS_VALUE_NAME flag for all axisValues
+ for axisValue in stat.AxisValueArray.AxisValue:
+ axisValue.Flags |= instancer.names.ELIDABLE_AXIS_VALUE_NAME
+
+ stat.ElidedFallbackNameID = 266 # Regular --> Black
+ instancer.names.updateNameTable(varfont, {"wght": 400})
+ # Since all axis values are elided, the elided fallback name
+ # must be used to construct the style names. Since we
+ # changed it to Black, we need both a typoSubFamilyName and
+ # the subFamilyName set so it conforms to the RIBBI model.
+ expected = {(2, 3, 1, 0x409): "Regular", (17, 3, 1, 0x409): "Black"}
+ _test_name_records(varfont, expected, isNonRIBBI=True)
+
+
+def test_updateNameTable_existing_subfamily_name_is_not_regular(varfont):
+ # Check the subFamily name will be set to Regular when we update a name
+ # table to a non-RIBBI style and the current subFamily name is a RIBBI
+ # style which isn't Regular.
+ varfont["name"].setName("Bold", 2, 3, 1, 0x409) # subFamily Regular --> Bold
+
+ instancer.names.updateNameTable(varfont, {"wght": 100})
+ expected = {(2, 3, 1, 0x409): "Regular", (17, 3, 1, 0x409): "Thin"}
+ _test_name_records(varfont, expected, isNonRIBBI=True)
diff --git a/Tests/varLib/interpolatable_test.py b/Tests/varLib/interpolatable_test.py
index 4900d522..a30be71e 100644
--- a/Tests/varLib/interpolatable_test.py
+++ b/Tests/varLib/interpolatable_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.varLib.interpolatable import main as interpolatable_main
import os
diff --git a/Tests/varLib/interpolate_layout_test.py b/Tests/varLib/interpolate_layout_test.py
index b858a3c9..d7134d79 100644
--- a/Tests/varLib/interpolate_layout_test.py
+++ b/Tests/varLib/interpolate_layout_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.varLib import build
from fontTools.varLib.interpolate_layout import interpolate_layout
@@ -749,8 +747,8 @@ class InterpolateLayoutTest(unittest.TestCase):
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
- def test_varlib_interpolate_layout_GPOS_only_LookupType_8_same_val_ttf(self):
- """Only GPOS; LookupType 8; same values in all masters.
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_7_same_val_ttf(self):
+ """Only GPOS; LookupType 7; same values in all masters.
"""
suffix = '.ttf'
ds_path = self.get_test_input('InterpolateLayout.designspace')
@@ -782,13 +780,13 @@ class InterpolateLayoutTest(unittest.TestCase):
instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_8_same.ttx')
+ expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_7_same.ttx')
self.expect_ttx(instfont, expected_ttx_path, tables)
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
- def test_varlib_interpolate_layout_GPOS_only_LookupType_8_diff_val_ttf(self):
- """Only GPOS; LookupType 8; different values in each master.
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_7_diff_val_ttf(self):
+ """Only GPOS; LookupType 7; different values in each master.
"""
suffix = '.ttf'
ds_path = self.get_test_input('InterpolateLayout.designspace')
@@ -834,7 +832,7 @@ class InterpolateLayoutTest(unittest.TestCase):
instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_8_diff.ttx')
+ expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_7_diff.ttx')
self.expect_ttx(instfont, expected_ttx_path, tables)
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
diff --git a/Tests/varLib/models_test.py b/Tests/varLib/models_test.py
index ae67e0e9..c220d3d2 100644
--- a/Tests/varLib/models_test.py
+++ b/Tests/varLib/models_test.py
@@ -1,7 +1,5 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.varLib.models import (
- normalizeLocation, supportScalar, VariationModel)
+ normalizeLocation, supportScalar, VariationModel, VariationModelError)
import pytest
@@ -146,7 +144,7 @@ class VariationModelTest(object):
assert model.deltaWeights == deltaWeights
def test_init_duplicate_locations(self):
- with pytest.raises(ValueError, match="locations must be unique"):
+ with pytest.raises(VariationModelError, match="Locations must be unique."):
VariationModel(
[
{"foo": 0.0, "bar": 0.0},
diff --git a/Tests/varLib/mutator_test.py b/Tests/varLib/mutator_test.py
index d781d59e..abe5d027 100644
--- a/Tests/varLib/mutator_test.py
+++ b/Tests/varLib/mutator_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.varLib import build
from fontTools.varLib.mutator import main as mutator
diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py
index ec05d565..b8e7183b 100644
--- a/Tests/varLib/varLib_test.py
+++ b/Tests/varLib/varLib_test.py
@@ -1,14 +1,16 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont, newTable
-from fontTools.varLib import build
+from fontTools.varLib import build, load_designspace
+from fontTools.varLib.errors import VarLibValidationError
+import fontTools.varLib.errors as varLibErrors
from fontTools.varLib.mutator import instantiateVariableFont
from fontTools.varLib import main as varLib_main, load_masters
from fontTools.varLib import set_default_weight_width_slant
from fontTools.designspaceLib import (
DesignSpaceDocumentError, DesignSpaceDocument, SourceDescriptor,
)
+from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
import difflib
+from io import BytesIO
import os
import shutil
import sys
@@ -21,6 +23,9 @@ def reload_font(font):
"""(De)serialize to get final binary layout."""
buf = BytesIO()
font.save(buf)
+ # Close the font to release filesystem resources so that on Windows the tearDown
+ # method can successfully remove the temporary directory created during setUp.
+ font.close()
buf.seek(0)
return TTFont(buf)
@@ -41,10 +46,15 @@ class BuildTest(unittest.TestCase):
if self.tempdir:
shutil.rmtree(self.tempdir)
- @staticmethod
- def get_test_input(test_file_or_folder):
- path, _ = os.path.split(__file__)
- return os.path.join(path, "data", test_file_or_folder)
+ def get_test_input(self, test_file_or_folder, copy=False):
+ parent_dir = os.path.dirname(__file__)
+ path = os.path.join(parent_dir, "data", test_file_or_folder)
+ if copy:
+ copied_path = os.path.join(self.tempdir, test_file_or_folder)
+ shutil.copy2(path, copied_path)
+ return copied_path
+ else:
+ return path
@staticmethod
def get_test_output(test_file_or_folder):
@@ -108,7 +118,8 @@ class BuildTest(unittest.TestCase):
return font, savepath
def _run_varlib_build_test(self, designspace_name, font_name, tables,
- expected_ttx_name, save_before_dump=False):
+ expected_ttx_name, save_before_dump=False,
+ post_process_master=None):
suffix = '.ttf'
ds_path = self.get_test_input(designspace_name + '.designspace')
ufo_dir = self.get_test_input('master_ufo')
@@ -117,7 +128,9 @@ class BuildTest(unittest.TestCase):
self.temp_dir()
ttx_paths = self.get_file_list(ttx_dir, '.ttx', font_name + '-')
for path in ttx_paths:
- self.compile_font(path, suffix, self.tempdir)
+ font, savepath = self.compile_font(path, suffix, self.tempdir)
+ if post_process_master is not None:
+ post_process_master(font, savepath)
finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
varfont, model, _ = build(ds_path, finder)
@@ -214,6 +227,83 @@ class BuildTest(unittest.TestCase):
save_before_dump=True,
)
+ def test_varlib_build_feature_variations_custom_tag(self):
+ """Designspace file contains <rules> element, used to build
+ GSUB FeatureVariations table.
+ """
+ self._run_varlib_build_test(
+ designspace_name="FeatureVarsCustomTag",
+ font_name="TestFamily",
+ tables=["fvar", "GSUB"],
+ expected_ttx_name="FeatureVarsCustomTag",
+ save_before_dump=True,
+ )
+
+ def test_varlib_build_feature_variations_whole_range(self):
+ """Designspace file contains <rules> element specifying the entire design
+ space, used to build GSUB FeatureVariations table.
+ """
+ self._run_varlib_build_test(
+ designspace_name="FeatureVarsWholeRange",
+ font_name="TestFamily",
+ tables=["fvar", "GSUB"],
+ expected_ttx_name="FeatureVarsWholeRange",
+ save_before_dump=True,
+ )
+
+ def test_varlib_build_feature_variations_whole_range_empty(self):
+ """Designspace file contains <rules> element without a condition, specifying
+ the entire design space, used to build GSUB FeatureVariations table.
+ """
+ self._run_varlib_build_test(
+ designspace_name="FeatureVarsWholeRangeEmpty",
+ font_name="TestFamily",
+ tables=["fvar", "GSUB"],
+ expected_ttx_name="FeatureVarsWholeRange",
+ save_before_dump=True,
+ )
+
+ def test_varlib_build_feature_variations_with_existing_rclt(self):
+ """Designspace file contains <rules> element, used to build GSUB
+ FeatureVariations table. <rules> is specified to do its OT processing
+ "last", so a 'rclt' feature will be used or created. This test covers
+ the case when a 'rclt' already exists in the masters.
+
+ We dynamically add a 'rclt' feature to an existing set of test
+ masters, to avoid adding more test data.
+
+ The multiple languages are done to verify whether multiple existing
+ 'rclt' features are updated correctly.
+ """
+ def add_rclt(font, savepath):
+ features = """
+ languagesystem DFLT dflt;
+ languagesystem latn dflt;
+ languagesystem latn NLD;
+
+ feature rclt {
+ script latn;
+ language NLD;
+ lookup A {
+ sub uni0041 by uni0061;
+ } A;
+ language dflt;
+ lookup B {
+ sub uni0041 by uni0061;
+ } B;
+ } rclt;
+ """
+ addOpenTypeFeaturesFromString(font, features)
+ font.save(savepath)
+ self._run_varlib_build_test(
+ designspace_name="FeatureVars",
+ font_name="TestFamily",
+ tables=["fvar", "GSUB"],
+ expected_ttx_name="FeatureVars_rclt",
+ save_before_dump=True,
+ post_process_master=add_rclt,
+ )
+
def test_varlib_gvar_explicit_delta(self):
"""The variable font contains a composite glyph odieresis which does not
need a gvar entry, because all its deltas are 0, but it must be added
@@ -230,11 +320,12 @@ class BuildTest(unittest.TestCase):
)
def test_varlib_nonmarking_CFF2(self):
- ds_path = self.get_test_input('TestNonMarkingCFF2.designspace')
+ self.temp_dir()
+
+ ds_path = self.get_test_input('TestNonMarkingCFF2.designspace', copy=True)
ttx_dir = self.get_test_input("master_non_marking_cff2")
expected_ttx_path = self.get_test_output("TestNonMarkingCFF2.ttx")
- self.temp_dir()
for path in self.get_file_list(ttx_dir, '.ttx', 'TestNonMarkingCFF2_'):
self.compile_font(path, ".otf", self.tempdir)
@@ -252,11 +343,35 @@ class BuildTest(unittest.TestCase):
self.expect_ttx(varfont, expected_ttx_path, tables)
def test_varlib_build_CFF2(self):
- ds_path = self.get_test_input('TestCFF2.designspace')
+ self.temp_dir()
+
+ ds_path = self.get_test_input('TestCFF2.designspace', copy=True)
ttx_dir = self.get_test_input("master_cff2")
expected_ttx_path = self.get_test_output("BuildTestCFF2.ttx")
+ for path in self.get_file_list(ttx_dir, '.ttx', 'TestCFF2_'):
+ self.compile_font(path, ".otf", self.tempdir)
+
+ ds = DesignSpaceDocument.fromfile(ds_path)
+ for source in ds.sources:
+ source.path = os.path.join(
+ self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf")
+ )
+ ds.updatePaths()
+
+ varfont, _, _ = build(ds)
+ varfont = reload_font(varfont)
+
+ tables = ["fvar", "CFF2"]
+ self.expect_ttx(varfont, expected_ttx_path, tables)
+
+ def test_varlib_build_CFF2_from_CFF2(self):
self.temp_dir()
+
+ ds_path = self.get_test_input('TestCFF2Input.designspace', copy=True)
+ ttx_dir = self.get_test_input("master_cff2_input")
+ expected_ttx_path = self.get_test_output("BuildTestCFF2.ttx")
+
for path in self.get_file_list(ttx_dir, '.ttx', 'TestCFF2_'):
self.compile_font(path, ".otf", self.tempdir)
@@ -274,11 +389,12 @@ class BuildTest(unittest.TestCase):
self.expect_ttx(varfont, expected_ttx_path, tables)
def test_varlib_build_sparse_CFF2(self):
- ds_path = self.get_test_input('TestSparseCFF2VF.designspace')
+ self.temp_dir()
+
+ ds_path = self.get_test_input('TestSparseCFF2VF.designspace', copy=True)
ttx_dir = self.get_test_input("master_sparse_cff2")
expected_ttx_path = self.get_test_output("TestSparseCFF2VF.ttx")
- self.temp_dir()
for path in self.get_file_list(ttx_dir, '.ttx', 'MasterSet_Kanji-'):
self.compile_font(path, ".otf", self.tempdir)
@@ -296,11 +412,12 @@ class BuildTest(unittest.TestCase):
self.expect_ttx(varfont, expected_ttx_path, tables)
def test_varlib_build_vpal(self):
- ds_path = self.get_test_input('test_vpal.designspace')
+ self.temp_dir()
+
+ ds_path = self.get_test_input('test_vpal.designspace', copy=True)
ttx_dir = self.get_test_input("master_vpal_test")
expected_ttx_path = self.get_test_output("test_vpal.ttx")
- self.temp_dir()
for path in self.get_file_list(ttx_dir, '.ttx', 'master_vpal_test_'):
self.compile_font(path, ".otf", self.tempdir)
@@ -388,11 +505,12 @@ class BuildTest(unittest.TestCase):
self.expect_ttx(varfont, expected_ttx_path, tables)
def test_varlib_build_from_ttf_paths(self):
- ds_path = self.get_test_input("Build.designspace")
+ self.temp_dir()
+
+ ds_path = self.get_test_input("Build.designspace", copy=True)
ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
expected_ttx_path = self.get_test_output("BuildMain.ttx")
- self.temp_dir()
for path in self.get_file_list(ttx_dir, '.ttx', 'TestFamily-'):
self.compile_font(path, ".ttf", self.tempdir)
@@ -434,6 +552,27 @@ class BuildTest(unittest.TestCase):
tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
self.expect_ttx(varfont, expected_ttx_path, tables)
+ def test_varlib_build_lazy_masters(self):
+ # See https://github.com/fonttools/fonttools/issues/1808
+ ds_path = self.get_test_input("SparseMasters.designspace")
+ expected_ttx_path = self.get_test_output("SparseMasters.ttx")
+
+ def _open_font(master_path, master_finder=lambda s: s):
+ font = TTFont()
+ font.importXML(master_path)
+ buf = BytesIO()
+ font.save(buf, reorderTables=False)
+ buf.seek(0)
+ font = TTFont(buf, lazy=True) # reopen in lazy mode, to reproduce #1808
+ return font
+
+ ds = DesignSpaceDocument.fromfile(ds_path)
+ ds.loadSourceFonts(_open_font)
+ varfont, _, _ = build(ds)
+ varfont = reload_font(varfont)
+ tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
+ self.expect_ttx(varfont, expected_ttx_path, tables)
+
def test_varlib_build_sparse_masters_MVAR(self):
import fontTools.varLib.mvar
@@ -516,12 +655,13 @@ class BuildTest(unittest.TestCase):
assert all(tag in mvar_tags for tag in fontTools.varLib.mvar.MVAR_ENTRIES)
def test_varlib_build_VVAR_CFF2(self):
- ds_path = self.get_test_input('TestVVAR.designspace')
+ self.temp_dir()
+
+ ds_path = self.get_test_input('TestVVAR.designspace', copy=True)
ttx_dir = self.get_test_input("master_vvar_cff2")
expected_ttx_name = 'TestVVAR'
suffix = '.otf'
- self.temp_dir()
for path in self.get_file_list(ttx_dir, '.ttx', 'TestVVAR'):
font, savepath = self.compile_font(path, suffix, self.tempdir)
@@ -540,6 +680,41 @@ class BuildTest(unittest.TestCase):
self.expect_ttx(varfont, expected_ttx_path, tables)
self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
+ def test_varlib_build_BASE(self):
+ self.temp_dir()
+
+ ds_path = self.get_test_input('TestBASE.designspace', copy=True)
+ ttx_dir = self.get_test_input("master_base_test")
+ expected_ttx_name = 'TestBASE'
+ suffix = '.otf'
+
+ for path in self.get_file_list(ttx_dir, '.ttx', 'TestBASE'):
+ font, savepath = self.compile_font(path, suffix, self.tempdir)
+
+ ds = DesignSpaceDocument.fromfile(ds_path)
+ for source in ds.sources:
+ source.path = os.path.join(
+ self.tempdir, os.path.basename(source.filename).replace(".ufo", suffix)
+ )
+ ds.updatePaths()
+
+ varfont, _, _ = build(ds)
+ varfont = reload_font(varfont)
+
+ expected_ttx_path = self.get_test_output(expected_ttx_name + '.ttx')
+ tables = ["BASE"]
+ self.expect_ttx(varfont, expected_ttx_path, tables)
+ self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
+
+ def test_varlib_build_single_master(self):
+ self._run_varlib_build_test(
+ designspace_name='SingleMaster',
+ font_name='TestFamily',
+ tables=['GDEF', 'HVAR', 'MVAR', 'STAT', 'fvar', 'cvar', 'gvar', 'name'],
+ expected_ttx_name='SingleMaster',
+ save_before_dump=True,
+ )
+
def test_kerning_merging(self):
"""Test the correct merging of class-based pair kerning.
@@ -632,6 +807,69 @@ class BuildTest(unittest.TestCase):
("B", "D"): 40,
}
+ def test_designspace_fill_in_location(self):
+ ds_path = self.get_test_input("VarLibLocationTest.designspace")
+ ds = DesignSpaceDocument.fromfile(ds_path)
+ ds_loaded = load_designspace(ds)
+
+ assert ds_loaded.instances[0].location == {"weight": 0, "width": 50}
+
+ def test_varlib_build_incompatible_features(self):
+ with pytest.raises(
+ varLibErrors.ShouldBeConstant,
+ match = """
+
+Couldn't merge the fonts, because some values were different, but should have
+been the same. This happened while performing the following operation:
+GPOS.table.FeatureList.FeatureCount
+
+The problem is likely to be in Simple Two Axis Bold:
+
+Incompatible features between masters.
+Expected: kern, mark.
+Got: kern.
+"""):
+
+ self._run_varlib_build_test(
+ designspace_name="IncompatibleFeatures",
+ font_name="IncompatibleFeatures",
+ tables=["GPOS"],
+ expected_ttx_name="IncompatibleFeatures",
+ save_before_dump=True,
+ )
+
+ def test_varlib_build_incompatible_lookup_types(self):
+ with pytest.raises(
+ varLibErrors.MismatchedTypes,
+ match = r"MarkBasePos, instead saw PairPos"
+ ):
+ self._run_varlib_build_test(
+ designspace_name="IncompatibleLookupTypes",
+ font_name="IncompatibleLookupTypes",
+ tables=["GPOS"],
+ expected_ttx_name="IncompatibleLookupTypes",
+ save_before_dump=True,
+ )
+
+ def test_varlib_build_incompatible_arrays(self):
+ with pytest.raises(
+ varLibErrors.ShouldBeConstant,
+ match = """
+
+Couldn't merge the fonts, because some values were different, but should have
+been the same. This happened while performing the following operation:
+GPOS.table.ScriptList.ScriptCount
+
+The problem is likely to be in Simple Two Axis Bold:
+Expected to see .ScriptCount==1, instead saw 0"""
+ ):
+ self._run_varlib_build_test(
+ designspace_name="IncompatibleArrays",
+ font_name="IncompatibleArrays",
+ tables=["GPOS"],
+ expected_ttx_name="IncompatibleArrays",
+ save_before_dump=True,
+ )
def test_load_masters_layerName_without_required_font():
ds = DesignSpaceDocument()
@@ -641,7 +879,7 @@ def test_load_masters_layerName_without_required_font():
ds.addSource(s)
with pytest.raises(
- AttributeError,
+ VarLibValidationError,
match="specified a layer name but lacks the required TTFont object",
):
load_masters(ds)
diff --git a/Tests/voltLib/lexer_test.py b/Tests/voltLib/lexer_test.py
index 6041cb85..2145d079 100644
--- a/Tests/voltLib/lexer_test.py
+++ b/Tests/voltLib/lexer_test.py
@@ -1,5 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
from fontTools.voltLib.error import VoltLibError
from fontTools.voltLib.lexer import Lexer
import unittest
diff --git a/Tests/voltLib/parser_test.py b/Tests/voltLib/parser_test.py
index 3bfa17c8..0e0191fc 100644
--- a/Tests/voltLib/parser_test.py
+++ b/Tests/voltLib/parser_test.py
@@ -1,9 +1,7 @@
-from __future__ import print_function, division, absolute_import
-from __future__ import unicode_literals
-from fontTools.misc.py23 import *
from fontTools.voltLib import ast
from fontTools.voltLib.error import VoltLibError
from fontTools.voltLib.parser import Parser
+from io import StringIO
import unittest
@@ -39,7 +37,7 @@ class ParserTest(unittest.TestCase):
("space", 3, [0x0020], "BASE", None))
def test_def_glyph_base_with_unicodevalues(self):
- [def_glyph] = self.parse(
+ [def_glyph] = self.parse_(
'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009" '
'TYPE BASE END_GLYPH'
).statements
@@ -57,7 +55,7 @@ class ParserTest(unittest.TestCase):
("CR", 2, [0x0009, 0x000D], "BASE", None))
def test_def_glyph_base_with_empty_unicodevalues(self):
- [def_glyph] = self.parse(
+ [def_glyph] = self.parse_(
'DEF_GLYPH "i.locl" ID 269 UNICODEVALUES "" '
'TYPE BASE END_GLYPH'
).statements
@@ -108,7 +106,7 @@ class ParserTest(unittest.TestCase):
def test_def_glyph_case_sensitive(self):
def_glyphs = self.parse(
'DEF_GLYPH "A" ID 3 UNICODE 65 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "a" ID 4 UNICODE 97 TYPE BASE END_GLYPH\n'
+ 'DEF_GLYPH "a" ID 4 UNICODE 97 TYPE BASE END_GLYPH'
).statements
self.assertEqual((def_glyphs[0].name, def_glyphs[0].id,
def_glyphs[0].unicode, def_glyphs[0].type,
@@ -122,10 +120,10 @@ class ParserTest(unittest.TestCase):
def test_def_group_glyphs(self):
[def_group] = self.parse(
'DEF_GROUP "aaccented"\n'
- 'ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" '
+ ' ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" '
'GLYPH "adieresis" GLYPH "ae" GLYPH "agrave" GLYPH "amacron" '
'GLYPH "aogonek" GLYPH "aring" GLYPH "atilde" END_ENUM\n'
- 'END_GROUP\n'
+ 'END_GROUP'
).statements
self.assertEqual((def_group.name, def_group.enum.glyphSet()),
("aaccented",
@@ -136,14 +134,14 @@ class ParserTest(unittest.TestCase):
def test_def_group_groups(self):
[group1, group2, test_group] = self.parse(
'DEF_GROUP "Group1"\n'
- 'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
+ ' ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "Group2"\n'
- 'ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
+ ' ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "TestGroup"\n'
- 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
- 'END_GROUP\n'
+ ' ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
+ 'END_GROUP'
).statements
groups = [g.group for g in test_group.enum.enum]
self.assertEqual((test_group.name, groups),
@@ -153,20 +151,20 @@ class ParserTest(unittest.TestCase):
[group1, test_group1, test_group2, test_group3, group2] = \
self.parse(
'DEF_GROUP "Group1"\n'
- 'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
+ ' ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "TestGroup1"\n'
- 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
+ ' ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "TestGroup2"\n'
- 'ENUM GROUP "Group2" END_ENUM\n'
+ ' ENUM GROUP "Group2" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "TestGroup3"\n'
- 'ENUM GROUP "Group2" GROUP "Group1" END_ENUM\n'
+ ' ENUM GROUP "Group2" GROUP "Group1" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "Group2"\n'
- 'ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
- 'END_GROUP\n'
+ ' ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
+ 'END_GROUP'
).statements
groups = [g.group for g in test_group1.enum.enum]
self.assertEqual(
@@ -197,12 +195,12 @@ class ParserTest(unittest.TestCase):
def test_def_group_glyphs_and_group(self):
[def_group1, def_group2] = self.parse(
'DEF_GROUP "aaccented"\n'
- 'ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" '
+ ' ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" '
'GLYPH "adieresis" GLYPH "ae" GLYPH "agrave" GLYPH "amacron" '
'GLYPH "aogonek" GLYPH "aring" GLYPH "atilde" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "KERN_lc_a_2ND"\n'
- 'ENUM GLYPH "a" GROUP "aaccented" END_ENUM\n'
+ ' ENUM GLYPH "a" GROUP "aaccented" END_ENUM\n'
'END_GROUP'
).statements
items = def_group2.enum.enum
@@ -221,7 +219,7 @@ class ParserTest(unittest.TestCase):
'DEF_GLYPH "ccedilla" ID 210 UNICODE 231 TYPE BASE END_GLYPH\n'
'DEF_GLYPH "cdotaccent" ID 210 UNICODE 267 TYPE BASE END_GLYPH\n'
'DEF_GROUP "KERN_lc_a_2ND"\n'
- 'ENUM RANGE "a" TO "atilde" GLYPH "b" RANGE "c" TO "cdotaccent" '
+ ' ENUM RANGE "a" TO "atilde" GLYPH "b" RANGE "c" TO "cdotaccent" '
'END_ENUM\n'
'END_GROUP'
).statements[-1]
@@ -240,7 +238,7 @@ class ParserTest(unittest.TestCase):
'END_GROUP\n'
'DEF_GROUP "dupe"\n'
'ENUM GLYPH "x" END_ENUM\n'
- 'END_GROUP\n'
+ 'END_GROUP'
)
def test_group_duplicate_case_insensitive(self):
@@ -253,12 +251,12 @@ class ParserTest(unittest.TestCase):
'END_GROUP\n'
'DEF_GROUP "Dupe"\n'
'ENUM GLYPH "x" END_ENUM\n'
- 'END_GROUP\n'
+ 'END_GROUP'
)
def test_script_without_langsys(self):
[script] = self.parse(
- 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'END_SCRIPT'
).statements
self.assertEqual((script.name, script.tag, script.langs),
@@ -266,10 +264,10 @@ class ParserTest(unittest.TestCase):
def test_langsys_normal(self):
[def_script] = self.parse(
- 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
- 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n'
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
+ 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
'END_LANGSYS\n'
- 'DEF_LANGSYS NAME "Moldavian" TAG "MOL "\n'
+ 'DEF_LANGSYS NAME "Moldavian" TAG "MOL "\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
@@ -287,8 +285,8 @@ class ParserTest(unittest.TestCase):
def test_langsys_no_script_name(self):
[langsys] = self.parse(
- 'DEF_SCRIPT TAG "latn"\n'
- 'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
+ 'DEF_SCRIPT TAG "latn"\n\n'
+ 'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
@@ -305,8 +303,8 @@ class ParserTest(unittest.TestCase):
VoltLibError,
r'.*Expected "TAG"'):
[langsys] = self.parse(
- 'DEF_SCRIPT NAME "Latin"\n'
- 'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
+ 'DEF_SCRIPT NAME "Latin"\n\n'
+ 'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
@@ -317,12 +315,12 @@ class ParserTest(unittest.TestCase):
'Script "DFLT" already defined, '
'script tags are case insensitive'):
[langsys1, langsys2] = self.parse(
- 'DEF_SCRIPT NAME "Default" TAG "DFLT"\n'
- 'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
+ 'DEF_SCRIPT NAME "Default" TAG "DFLT"\n\n'
+ 'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
'END_SCRIPT\n'
- 'DEF_SCRIPT TAG "DFLT"\n'
- 'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
+ 'DEF_SCRIPT TAG "DFLT"\n\n'
+ 'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
@@ -338,21 +336,21 @@ class ParserTest(unittest.TestCase):
'END_LANGSYS\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
'END_LANGSYS\n'
- 'END_SCRIPT\n'
+ 'END_SCRIPT'
).statements
def test_langsys_lang_in_separate_scripts(self):
[langsys1, langsys2] = self.parse(
- 'DEF_SCRIPT NAME "Default" TAG "DFLT"\n'
- 'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
+ 'DEF_SCRIPT NAME "Default" TAG "DFLT"\n\n'
+ 'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
- 'DEF_LANGSYS NAME "Default" TAG "ROM "\n'
+ 'DEF_LANGSYS NAME "Default" TAG "ROM "\n\n'
'END_LANGSYS\n'
'END_SCRIPT\n'
- 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
- 'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
+ 'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
'END_LANGSYS\n'
- 'DEF_LANGSYS NAME "Default" TAG "ROM "\n'
+ 'DEF_LANGSYS NAME "Default" TAG "ROM "\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
@@ -363,8 +361,8 @@ class ParserTest(unittest.TestCase):
def test_langsys_no_lang_name(self):
[langsys] = self.parse(
- 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
- 'DEF_LANGSYS TAG "dflt"\n'
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
+ 'DEF_LANGSYS TAG "dflt"\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
@@ -381,18 +379,18 @@ class ParserTest(unittest.TestCase):
VoltLibError,
r'.*Expected "TAG"'):
[langsys] = self.parse(
- 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
- 'DEF_LANGSYS NAME "Default"\n'
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
+ 'DEF_LANGSYS NAME "Default"\n\n'
'END_LANGSYS\n'
'END_SCRIPT'
).statements
def test_feature(self):
[def_script] = self.parse(
- 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
- 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n'
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
+ 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
'DEF_FEATURE NAME "Fractions" TAG "frac"\n'
- 'LOOKUP "fraclookup"\n'
+ ' LOOKUP "fraclookup"\n'
'END_FEATURE\n'
'END_LANGSYS\n'
'END_SCRIPT'
@@ -404,10 +402,10 @@ class ParserTest(unittest.TestCase):
"frac",
["fraclookup"]))
[def_script] = self.parse(
- 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
- 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n'
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
+ 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
'DEF_FEATURE NAME "Kerning" TAG "kern"\n'
- 'LOOKUP "kern1" LOOKUP "kern2"\n'
+ ' LOOKUP "kern1" LOOKUP "kern2"\n'
'END_FEATURE\n'
'END_LANGSYS\n'
'END_SCRIPT'
@@ -475,6 +473,21 @@ class ParserTest(unittest.TestCase):
'END_SUBSTITUTION\n'
).statements
+ def test_lookup_comments(self):
+ [lookup] = self.parse(
+ 'DEF_LOOKUP "test" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR\n'
+ 'COMMENTS "Hello\\nWorld"\n'
+ 'IN_CONTEXT\n'
+ 'END_CONTEXT\n'
+ 'AS_SUBSTITUTION\n'
+ 'SUB GLYPH "a"\n'
+ 'WITH GLYPH "b"\n'
+ 'END_SUB\n'
+ 'END_SUBSTITUTION'
+ ).statements
+ self.assertEqual(lookup.name, "test")
+ self.assertEqual(lookup.comments, "Hello\nWorld")
+
def test_substitution_empty(self):
with self.assertRaisesRegex(
VoltLibError,
@@ -559,12 +572,13 @@ class ParserTest(unittest.TestCase):
def test_substitution_single_in_context(self):
[group, lookup] = self.parse(
- 'DEF_GROUP "Denominators" ENUM GLYPH "one.dnom" GLYPH "two.dnom" '
- 'END_ENUM END_GROUP\n'
+ 'DEF_GROUP "Denominators"\n'
+ ' ENUM GLYPH "one.dnom" GLYPH "two.dnom" END_ENUM\n'
+ 'END_GROUP\n'
'DEF_LOOKUP "fracdnom" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
- 'IN_CONTEXT LEFT ENUM GROUP "Denominators" GLYPH "fraction" '
- 'END_ENUM\n'
+ 'IN_CONTEXT\n'
+ ' LEFT ENUM GROUP "Denominators" GLYPH "fraction" END_ENUM\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "one"\n'
@@ -590,17 +604,18 @@ class ParserTest(unittest.TestCase):
def test_substitution_single_in_contexts(self):
[group, lookup] = self.parse(
- 'DEF_GROUP "Hebrew" ENUM GLYPH "uni05D0" GLYPH "uni05D1" '
- 'END_ENUM END_GROUP\n'
+ 'DEF_GROUP "Hebrew"\n'
+ ' ENUM GLYPH "uni05D0" GLYPH "uni05D1" END_ENUM\n'
+ 'END_GROUP\n'
'DEF_LOOKUP "HebrewCurrency" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
- 'RIGHT GROUP "Hebrew"\n'
- 'RIGHT GLYPH "one.Hebr"\n'
+ ' RIGHT GROUP "Hebrew"\n'
+ ' RIGHT GLYPH "one.Hebr"\n'
'END_CONTEXT\n'
'IN_CONTEXT\n'
- 'LEFT GROUP "Hebrew"\n'
- 'LEFT GLYPH "one.Hebr"\n'
+ ' LEFT GROUP "Hebrew"\n'
+ ' LEFT GLYPH "one.Hebr"\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "dollar"\n'
@@ -631,8 +646,9 @@ class ParserTest(unittest.TestCase):
def test_substitution_skip_base(self):
[group, lookup] = self.parse(
- 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" '
- 'END_ENUM END_GROUP\n'
+ 'DEF_GROUP "SomeMarks"\n'
+ ' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
+ 'END_GROUP\n'
'DEF_LOOKUP "SomeSub" SKIP_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
@@ -649,8 +665,9 @@ class ParserTest(unittest.TestCase):
def test_substitution_process_base(self):
[group, lookup] = self.parse(
- 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" '
- 'END_ENUM END_GROUP\n'
+ 'DEF_GROUP "SomeMarks"\n'
+ ' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
+ 'END_GROUP\n'
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
@@ -665,12 +682,74 @@ class ParserTest(unittest.TestCase):
(lookup.name, lookup.process_base),
("SomeSub", True))
+ def test_substitution_process_marks(self):
+ [group, lookup] = self.parse(
+ 'DEF_GROUP "SomeMarks"\n'
+ ' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
+ 'END_GROUP\n'
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "SomeMarks"\n'
+ 'IN_CONTEXT\n'
+ 'END_CONTEXT\n'
+ 'AS_SUBSTITUTION\n'
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ 'END_SUB\n'
+ 'END_SUBSTITUTION'
+ ).statements
+ self.assertEqual(
+ (lookup.name, lookup.process_marks),
+ ("SomeSub", 'SomeMarks'))
+
+ def test_substitution_process_marks_all(self):
+ [lookup] = self.parse(
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL\n'
+ 'IN_CONTEXT\n'
+ 'END_CONTEXT\n'
+ 'AS_SUBSTITUTION\n'
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ 'END_SUB\n'
+ 'END_SUBSTITUTION'
+ ).statements
+ self.assertEqual(
+ (lookup.name, lookup.process_marks),
+ ("SomeSub", True))
+
+ def test_substitution_process_marks_none(self):
+ [lookup] = self.parse_(
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "NONE"\n'
+ 'IN_CONTEXT\n'
+ 'END_CONTEXT\n'
+ 'AS_SUBSTITUTION\n'
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ 'END_SUB\n'
+ 'END_SUBSTITUTION'
+ ).statements
+ self.assertEqual(
+ (lookup.name, lookup.process_marks),
+ ("SomeSub", False))
+
+ def test_substitution_process_marks_bad(self):
+ with self.assertRaisesRegex(
+ VoltLibError,
+ 'Expected ALL, NONE, MARK_GLYPH_SET or an ID'):
+ self.parse(
+ 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" '
+ 'END_ENUM END_GROUP\n'
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS SomeMarks '
+ 'AS_SUBSTITUTION\n'
+ 'SUB GLYPH "A" WITH GLYPH "A.c2sc"\n'
+ 'END_SUB\n'
+ 'END_SUBSTITUTION'
+ )
+
def test_substitution_skip_marks(self):
[group, lookup] = self.parse(
- 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" '
- 'END_ENUM END_GROUP\n'
- 'DEF_LOOKUP "SomeSub" PROCESS_BASE SKIP_MARKS '
- 'DIRECTION LTR\n'
+ 'DEF_GROUP "SomeMarks"\n'
+ ' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
+ 'END_GROUP\n'
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE SKIP_MARKS DIRECTION LTR\n'
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
@@ -685,11 +764,13 @@ class ParserTest(unittest.TestCase):
def test_substitution_mark_attachment(self):
[group, lookup] = self.parse(
- 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" '
- 'END_ENUM END_GROUP\n'
+ 'DEF_GROUP "SomeMarks"\n'
+ ' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
+ 'END_GROUP\n'
'DEF_LOOKUP "SomeSub" PROCESS_BASE '
- 'PROCESS_MARKS "SomeMarks" \n'
- 'DIRECTION RTL\n'
+ 'PROCESS_MARKS "SomeMarks" DIRECTION RTL\n'
+ 'IN_CONTEXT\n'
+ 'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
@@ -702,11 +783,13 @@ class ParserTest(unittest.TestCase):
def test_substitution_mark_glyph_set(self):
[group, lookup] = self.parse(
- 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" '
- 'END_ENUM END_GROUP\n'
+ 'DEF_GROUP "SomeMarks"\n'
+ ' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
+ 'END_GROUP\n'
'DEF_LOOKUP "SomeSub" PROCESS_BASE '
- 'PROCESS_MARKS MARK_GLYPH_SET "SomeMarks" \n'
- 'DIRECTION RTL\n'
+ 'PROCESS_MARKS MARK_GLYPH_SET "SomeMarks" DIRECTION RTL\n'
+ 'IN_CONTEXT\n'
+ 'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
@@ -719,11 +802,13 @@ class ParserTest(unittest.TestCase):
def test_substitution_process_all_marks(self):
[group, lookup] = self.parse(
- 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" '
- 'END_ENUM END_GROUP\n'
- 'DEF_LOOKUP "SomeSub" PROCESS_BASE '
- 'PROCESS_MARKS ALL \n'
+ 'DEF_GROUP "SomeMarks"\n'
+ ' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
+ 'END_GROUP\n'
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION RTL\n'
+ 'IN_CONTEXT\n'
+ 'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
@@ -740,7 +825,7 @@ class ParserTest(unittest.TestCase):
'DEF_LOOKUP "Lookup" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
- 'RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
+ ' RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GLYPH "a"\n'
@@ -756,15 +841,15 @@ class ParserTest(unittest.TestCase):
def test_substitution_reversal(self):
lookup = self.parse(
'DEF_GROUP "DFLT_Num_standardFigures"\n'
- 'ENUM GLYPH "zero" GLYPH "one" GLYPH "two" END_ENUM\n'
+ ' ENUM GLYPH "zero" GLYPH "one" GLYPH "two" END_ENUM\n'
'END_GROUP\n'
'DEF_GROUP "DFLT_Num_numerators"\n'
- 'ENUM GLYPH "zero.numr" GLYPH "one.numr" GLYPH "two.numr" END_ENUM\n'
+ ' ENUM GLYPH "zero.numr" GLYPH "one.numr" GLYPH "two.numr" END_ENUM\n'
'END_GROUP\n'
'DEF_LOOKUP "RevLookup" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR REVERSAL\n'
'IN_CONTEXT\n'
- 'RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
+ ' RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
'END_CONTEXT\n'
'AS_SUBSTITUTION\n'
'SUB GROUP "DFLT_Num_standardFigures"\n'
@@ -820,7 +905,7 @@ class ParserTest(unittest.TestCase):
'DEF_LOOKUP "numr" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR REVERSAL\n'
'IN_CONTEXT\n'
- 'RIGHT ENUM '
+ ' RIGHT ENUM '
'GLYPH "fraction" '
'RANGE "zero.numr" TO "nine.numr" '
'END_ENUM\n'
@@ -861,7 +946,7 @@ class ParserTest(unittest.TestCase):
'DEF_LOOKUP "empty_position" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'EXCEPT_CONTEXT\n'
- 'LEFT GLYPH "glyph"\n'
+ ' LEFT GLYPH "glyph"\n'
'END_CONTEXT\n'
'AS_POSITION\n'
'END_POSITION'
@@ -880,13 +965,13 @@ class ParserTest(unittest.TestCase):
'END_ATTACH\n'
'END_POSITION\n'
'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb COMPONENT 1 '
- 'AT POS DX 0 DY 450 END_POS END_ANCHOR\n'
+ 'AT POS DX 0 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "MARK_top" ON 121 GLYPH gravecomb COMPONENT 1 '
- 'AT POS DX 0 DY 450 END_POS END_ANCHOR\n'
+ 'AT POS DX 0 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "top" ON 31 GLYPH a COMPONENT 1 '
- 'AT POS DX 210 DY 450 END_POS END_ANCHOR\n'
+ 'AT POS DX 210 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "top" ON 35 GLYPH e COMPONENT 1 '
- 'AT POS DX 215 DY 450 END_POS END_ANCHOR\n'
+ 'AT POS DX 215 DY 450 END_POS END_ANCHOR'
).statements
pos = lookup.pos
coverage = [g.glyph for g in pos.coverage]
@@ -926,9 +1011,9 @@ class ParserTest(unittest.TestCase):
'IN_CONTEXT\n'
'END_CONTEXT\n'
'AS_POSITION\n'
- 'ATTACH_CURSIVE EXIT GLYPH "a" GLYPH "b" ENTER GLYPH "c"\n'
+ 'ATTACH_CURSIVE\nEXIT GLYPH "a" GLYPH "b"\nENTER GLYPH "c"\n'
'END_ATTACH\n'
- 'END_POSITION\n'
+ 'END_POSITION'
).statements
exit = [[g.glyph for g in v] for v in lookup.pos.coverages_exit]
enter = [[g.glyph for g in v] for v in lookup.pos.coverages_enter]
@@ -945,12 +1030,12 @@ class ParserTest(unittest.TestCase):
'END_CONTEXT\n'
'AS_POSITION\n'
'ADJUST_PAIR\n'
- ' FIRST GLYPH "A"\n'
- ' SECOND GLYPH "V"\n'
+ ' FIRST GLYPH "A"\n'
+ ' SECOND GLYPH "V"\n'
' 1 2 BY POS ADV -30 END_POS POS END_POS\n'
- ' 2 1 BY POS ADV -30 END_POS POS END_POS\n'
+ ' 2 1 BY POS ADV -30 END_POS POS END_POS\n\n'
'END_ADJUST\n'
- 'END_POSITION\n'
+ 'END_POSITION'
).statements
coverages_1 = [[g.glyph for g in v] for v in lookup.pos.coverages_1]
coverages_2 = [[g.glyph for g in v] for v in lookup.pos.coverages_2]
@@ -969,15 +1054,15 @@ class ParserTest(unittest.TestCase):
'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR\n'
'IN_CONTEXT\n'
- # 'LEFT GLYPH "leftGlyph"\n'
- # 'RIGHT GLYPH "rightGlyph"\n'
+ # ' LEFT GLYPH "leftGlyph"\n'
+ # ' RIGHT GLYPH "rightGlyph"\n'
'END_CONTEXT\n'
'AS_POSITION\n'
'ADJUST_SINGLE'
- ' GLYPH "glyph1" BY POS ADV 0 DX 123 END_POS\n'
+ ' GLYPH "glyph1" BY POS ADV 0 DX 123 END_POS'
' GLYPH "glyph2" BY POS ADV 0 DX 456 END_POS\n'
'END_ADJUST\n'
- 'END_POSITION\n'
+ 'END_POSITION'
).statements
pos = lookup.pos
adjust = [[[g.glyph for g in a], b] for (a, b) in pos.adjust_single]
@@ -991,11 +1076,11 @@ class ParserTest(unittest.TestCase):
def test_def_anchor(self):
[anchor1, anchor2, anchor3] = self.parse(
'DEF_ANCHOR "top" ON 120 GLYPH a '
- 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
+ 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb '
- 'COMPONENT 1 AT POS DX 0 DY 450 END_POS END_ANCHOR\n'
+ 'COMPONENT 1 AT POS DX 0 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "bottom" ON 120 GLYPH a '
- 'COMPONENT 1 AT POS DX 250 DY 0 END_POS END_ANCHOR\n'
+ 'COMPONENT 1 AT POS DX 250 DY 0 END_POS END_ANCHOR'
).statements
self.assertEqual(
(anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component,
@@ -1019,9 +1104,9 @@ class ParserTest(unittest.TestCase):
def test_def_anchor_multi_component(self):
[anchor1, anchor2] = self.parse(
'DEF_ANCHOR "top" ON 120 GLYPH a '
- 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
+ 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "top" ON 120 GLYPH a '
- 'COMPONENT 2 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
+ 'COMPONENT 2 AT POS DX 250 DY 450 END_POS END_ANCHOR'
).statements
self.assertEqual(
(anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component),
@@ -1039,15 +1124,15 @@ class ParserTest(unittest.TestCase):
'anchor names are case insensitive',
self.parse,
'DEF_ANCHOR "dupe" ON 120 GLYPH a '
- 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
+ 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
'DEF_ANCHOR "dupe" ON 120 GLYPH a '
- 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
+ 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR'
)
def test_def_anchor_locked(self):
[anchor] = self.parse(
'DEF_ANCHOR "top" ON 120 GLYPH a '
- 'COMPONENT 1 LOCKED AT POS DX 250 DY 450 END_POS END_ANCHOR\n'
+ 'COMPONENT 1 LOCKED AT POS DX 250 DY 450 END_POS END_ANCHOR'
).statements
self.assertEqual(
(anchor.name, anchor.gid, anchor.glyph_name, anchor.component,
@@ -1059,7 +1144,7 @@ class ParserTest(unittest.TestCase):
def test_anchor_adjust_device(self):
[anchor] = self.parse(
'DEF_ANCHOR "MARK_top" ON 123 GLYPH diacglyph '
- 'COMPONENT 1 AT POS DX 0 DY 456 ADJUST_BY 12 AT 34 '
+ 'COMPONENT 1 AT POS DX 0 DY 456 ADJUST_BY 12 AT 34 '
'ADJUST_BY 56 AT 78 END_POS END_ANCHOR'
).statements
self.assertEqual(
@@ -1071,7 +1156,7 @@ class ParserTest(unittest.TestCase):
[grid_ppem, pres_ppem, ppos_ppem] = self.parse(
'GRID_PPEM 20\n'
'PRESENTATION_PPEM 72\n'
- 'PPOSITIONING_PPEM 144\n'
+ 'PPOSITIONING_PPEM 144'
).statements
self.assertEqual(
((grid_ppem.name, grid_ppem.value),
@@ -1084,7 +1169,7 @@ class ParserTest(unittest.TestCase):
def test_compiler_flags(self):
[setting1, setting2] = self.parse(
'COMPILER_USEEXTENSIONLOOKUPS\n'
- 'COMPILER_USEPAIRPOSFORMAT2\n'
+ 'COMPILER_USEPAIRPOSFORMAT2'
).statements
self.assertEqual(
((setting1.name, setting1.value),
@@ -1097,7 +1182,7 @@ class ParserTest(unittest.TestCase):
[cmap_format1, cmap_format2, cmap_format3] = self.parse(
'CMAP_FORMAT 0 3 4\n'
'CMAP_FORMAT 1 0 6\n'
- 'CMAP_FORMAT 3 1 4\n'
+ 'CMAP_FORMAT 3 1 4'
).statements
self.assertEqual(
((cmap_format1.name, cmap_format1.value),
@@ -1108,16 +1193,42 @@ class ParserTest(unittest.TestCase):
("CMAP_FORMAT", (3, 1, 4)))
)
+ def test_do_not_touch_cmap(self):
+ [option1, option2, option3, option4] = self.parse(
+ 'DO_NOT_TOUCH_CMAP\n'
+ 'CMAP_FORMAT 0 3 4\n'
+ 'CMAP_FORMAT 1 0 6\n'
+ 'CMAP_FORMAT 3 1 4'
+ ).statements
+ self.assertEqual(
+ ((option1.name, option1.value),
+ (option2.name, option2.value),
+ (option3.name, option3.value),
+ (option4.name, option4.value)),
+ (("DO_NOT_TOUCH_CMAP", True),
+ ("CMAP_FORMAT", (0, 3, 4)),
+ ("CMAP_FORMAT", (1, 0, 6)),
+ ("CMAP_FORMAT", (3, 1, 4)))
+ )
+
def test_stop_at_end(self):
- [def_glyph] = self.parse(
+ doc = self.parse_(
'DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH END\0\0\0\0'
- ).statements
+ )
+ [def_glyph] = doc.statements
self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
def_glyph.type, def_glyph.components),
(".notdef", 0, None, "BASE", None))
+ self.assertEqual(str(doc),
+ '\nDEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH END\n')
+
+ def parse_(self, text):
+ return Parser(StringIO(text)).parse()
def parse(self, text):
- return Parser(UnicodeIO(text)).parse()
+ doc = self.parse_(text)
+ self.assertEqual('\n'.join(str(s) for s in doc.statements), text)
+ return Parser(StringIO(text)).parse()
if __name__ == "__main__":
import sys
diff --git a/dev-requirements.txt b/dev-requirements.txt
index a34deb2e..73eae680 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -2,3 +2,4 @@ pytest>=3.0
tox>=2.5
bump2version>=0.5.6
sphinx>=1.5.5
+mypy>=0.782
diff --git a/fonttools b/fonttools
index 92b390e7..90bc63f9 100755
--- a/fonttools
+++ b/fonttools
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-from __future__ import print_function, division, absolute_import
+#!/usr/bin/env python3
import sys
import os.path
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 00000000..7e37b03f
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,21 @@
+[mypy]
+python_version = 3.6
+files = Lib/fontTools/misc/plistlib
+follow_imports = silent
+ignore_missing_imports = True
+warn_redundant_casts = True
+warn_unused_configs = True
+warn_unused_ignores = True
+
+[mypy-fontTools.misc.plistlib]
+check_untyped_defs = True
+disallow_any_generics = True
+disallow_incomplete_defs = True
+disallow_subclassing_any = True
+disallow_untyped_decorators = True
+disallow_untyped_calls = False
+disallow_untyped_defs = True
+no_implicit_optional = True
+no_implicit_reexport = True
+strict_equality = True
+warn_return_any = True
diff --git a/requirements.txt b/requirements.txt
index ee665b6e..680ffbb5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,15 +1,13 @@
# we use the official Brotli module on CPython and the CFFI-based
# extension 'brotlipy' on PyPy
-brotli==1.0.7; platform_python_implementation != "PyPy"
+brotli==1.0.9; platform_python_implementation != "PyPy"
brotlipy==0.7.0; platform_python_implementation == "PyPy"
-unicodedata2==12.0.0; python_version < '3.8' and platform_python_implementation != "PyPy"
-scipy==1.2.1; platform_python_implementation != "PyPy" and python_version < '3.5' # pyup: ignore
-scipy==1.3.0; platform_python_implementation != "PyPy" and python_version >= '3.5'
-munkres==1.0.12; platform_python_implementation == "PyPy" and python_version < '3.5' # pyup: ignore
-munkres==1.1.2; platform_python_implementation == "PyPy" and python_version >= '3.5'
+unicodedata2==13.0.0.post2; python_version < '3.9' and platform_python_implementation != "PyPy"
+scipy==1.5.4; platform_python_implementation != "PyPy"
+munkres==1.1.4; platform_python_implementation == "PyPy"
zopfli==0.1.6
-fs==2.4.9
-# lxml 4.4.0 breaks OrderedDict attributes in python < 3.6 so we pin to previous version
-# https://bugs.launchpad.net/lxml/+bug/1838252
-lxml==4.3.5; python_version < '3.6' # pyup: ignore
-lxml==4.4.0; python_version >= '3.6'
+fs==2.4.11
+skia-pathops==0.5.1.post1; platform_python_implementation != "PyPy"
+# this is only required to run Tests/cu2qu/{ufo,cli}_test.py
+ufoLib2==0.6.2
+pyobjc==6.2.2; sys_platform == "darwin"
diff --git a/run-tests.sh b/run-tests.sh
deleted file mode 100755
index f10c1b01..00000000
--- a/run-tests.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-# exit if any subcommand return non-zero status
-set -e
-
-# Choose python version
-if test "x$1" = x-3; then
- PYTHON=py3
- shift
-elif test "x$1" = x-2; then
- PYTHON=py2
- shift
-fi
-test "x$PYTHON" = x && PYTHON=py
-
-# Find tests
-FILTERS=
-for arg in "$@"; do
- test "x$FILTERS" != x && FILTERS="$FILTERS or "
- FILTERS="$FILTERS$arg"
-done
-
-# Run tests
-if [ -z "$FILTERS" ]; then
- tox --develop -e $PYTHON
-else
- tox --develop -e $PYTHON -- -k "$FILTERS"
-fi
diff --git a/setup.cfg b/setup.cfg
index e8f4864e..230175c6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 3.44.0
+current_version = 4.22.0
commit = True
tag = False
tag_name = {new_version}
@@ -24,9 +24,6 @@ replace = __version__ = "{new_version}"
search = version="{current_version}"
replace = version="{new_version}"
-[wheel]
-universal = 1
-
[sdist]
formats = zip
@@ -37,6 +34,7 @@ license_file = LICENSE
minversion = 3.0
testpaths =
Tests
+ fontTools
python_files =
*_test.py
python_classes =
@@ -50,14 +48,7 @@ doctest_optionflags =
ALLOW_UNICODE
ELLIPSIS
filterwarnings =
- ignore:tostring:DeprecationWarning
- ignore:fromstring:DeprecationWarning
ignore:readPlist:DeprecationWarning:plistlib_test
ignore:writePlist:DeprecationWarning:plistlib_test
ignore:some_function:DeprecationWarning:fontTools.ufoLib.utils
ignore::DeprecationWarning:fontTools.varLib.designspace
-
-[egg_info]
-tag_build =
-tag_date = 0
-
diff --git a/setup.py b/setup.py
index c259cfdf..d1c5a4f6 100755
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
from __future__ import print_function
import io
@@ -6,7 +6,8 @@ import sys
import os
from os.path import isfile, join as pjoin
from glob import glob
-from setuptools import setup, find_packages, Command
+from setuptools import setup, find_packages, Command, Extension
+from setuptools.command.build_ext import build_ext as _build_ext
from distutils import log
from distutils.util import convert_path
import subprocess as sp
@@ -23,33 +24,66 @@ def doraise_py_compile(file, cfile=None, dfile=None, doraise=False):
py_compile.compile = doraise_py_compile
-needs_wheel = {'bdist_wheel'}.intersection(sys.argv)
-wheel = ['wheel'] if needs_wheel else []
-needs_bumpversion = {'release'}.intersection(sys.argv)
-bumpversion = ['bump2version'] if needs_bumpversion else []
+setup_requires = []
+
+if {'bdist_wheel'}.intersection(sys.argv):
+ setup_requires.append('wheel')
+
+if {'release'}.intersection(sys.argv):
+ setup_requires.append('bump2version')
+
+try:
+ __import__("cython")
+except ImportError:
+ has_cython = False
+else:
+ has_cython = True
+
+env_with_cython = os.environ.get("FONTTOOLS_WITH_CYTHON")
+with_cython = (
+ True if env_with_cython in {"1", "true", "yes"}
+ else False if env_with_cython in {"0", "false", "no"}
+ else None
+)
+# --with-cython/--without-cython options override environment variables
+opt_with_cython = {'--with-cython'}.intersection(sys.argv)
+opt_without_cython = {'--without-cython'}.intersection(sys.argv)
+if opt_with_cython and opt_without_cython:
+ sys.exit(
+ "error: the options '--with-cython' and '--without-cython' are "
+ "mutually exclusive"
+ )
+elif opt_with_cython:
+ sys.argv.remove("--with-cython")
+ with_cython = True
+elif opt_without_cython:
+ sys.argv.remove("--without-cython")
+ with_cython = False
+
+if with_cython and not has_cython:
+ setup_requires.append("cython")
+
+ext_modules = []
+if with_cython is True or (with_cython is None and has_cython):
+ ext_modules.append(
+ Extension("fontTools.cu2qu.cu2qu", ["Lib/fontTools/cu2qu/cu2qu.py"]),
+ )
extras_require = {
# for fontTools.ufoLib: to read/write UFO fonts
"ufo": [
"fs >= 2.2.0, < 3",
- "enum34 >= 1.1.6; python_version < '3.4'",
],
# for fontTools.misc.etree and fontTools.misc.plistlib: use lxml to
# read/write XML files (faster/safer than built-in ElementTree)
"lxml": [
"lxml >= 4.0, < 5",
- "singledispatch >= 3.4.0.3; python_version < '3.4'",
- # typing >= 3.6.4 is required when using ABC collections with the
- # singledispatch backport, see:
- # https://github.com/fonttools/fonttools/issues/1423
- # https://github.com/python/typing/issues/484
- "typing >= 3.6.4; python_version < '3.4'",
],
# for fontTools.sfnt and fontTools.woff2: to compress/uncompress
# WOFF 1.0 and WOFF 2.0 webfonts.
"woff": [
- "brotli >= 1.0.1; platform_python_implementation != 'PyPy'",
- "brotlipy >= 0.7.0; platform_python_implementation == 'PyPy'",
+ "brotli >= 1.0.1; platform_python_implementation == 'CPython'",
+ "brotlicffi >= 0.8.0; platform_python_implementation != 'CPython'",
"zopfli >= 0.1.4",
],
# for fontTools.unicode and fontTools.unicodedata: to use the latest version
@@ -57,10 +91,10 @@ extras_require = {
# which varies between python versions and may be outdated.
"unicode": [
# the unicodedata2 extension module doesn't work on PyPy.
- # Python 3.8 already has Unicode 12, so the backport is not needed.
+ # Python 3.9 already has Unicode 13.0, so the backport is not needed.
(
- "unicodedata2 >= 12.0.0; "
- "python_version < '3.8' and platform_python_implementation != 'PyPy'"
+ "unicodedata2 >= 13.0.0; "
+ "python_version < '3.9' and platform_python_implementation != 'PyPy'"
),
],
# for graphite type tables in ttLib/tables (Silf, Glat, Gloc)
@@ -88,6 +122,10 @@ extras_require = {
"type1": [
"xattr; sys_platform == 'darwin'",
],
+ # for fontTools.ttLib.removeOverlaps, to remove overlaps in TTF fonts
+ "pathops": [
+ "skia-pathops >= 0.5.0",
+ ],
}
# use a special 'all' key as shorthand to includes all the extra dependencies
extras_require["all"] = sum(extras_require.values(), [])
@@ -261,7 +299,7 @@ class release(Command):
""" Run bumpversion.main() with the specified arguments, and return the
new computed version string (cf. 'bumpversion --help' for more info)
"""
- import bumpversion
+ import bumpversion.cli
args = (
(['--verbose'] if self.verbose > 1 else []) +
@@ -274,7 +312,7 @@ class release(Command):
log.debug("$ bumpversion %s" % " ".join(a.replace(" ", "\\ ") for a in args))
with capture_logger("bumpversion.list") as out:
- bumpversion.main(args)
+ bumpversion.cli.main(args)
last_line = out.getvalue().splitlines()[-1]
new_version = last_line.replace("new_version=", "")
@@ -350,9 +388,60 @@ def find_data_files(manpath="share/man"):
return data_files
-setup(
+class cython_build_ext(_build_ext):
+ """Compile *.pyx source files to *.c using cythonize if Cython is
+ installed and there is a working C compiler, else fall back to pure python dist.
+ """
+
+ def finalize_options(self):
+ from Cython.Build import cythonize
+
+ # optionally enable line tracing for test coverage support
+ linetrace = os.environ.get("CYTHON_TRACE") == "1"
+
+ self.distribution.ext_modules[:] = cythonize(
+ self.distribution.ext_modules,
+ force=linetrace or self.force,
+ annotate=os.environ.get("CYTHON_ANNOTATE") == "1",
+ quiet=not self.verbose,
+ compiler_directives={
+ "linetrace": linetrace,
+ "language_level": 3,
+ "embedsignature": True,
+ },
+ )
+
+ _build_ext.finalize_options(self)
+
+ def build_extensions(self):
+ try:
+ _build_ext.build_extensions(self)
+ except Exception as e:
+ if with_cython:
+ raise
+ from distutils.errors import DistutilsModuleError
+
+ # optional compilation failed: we delete 'ext_modules' and make sure
+ # the generated wheel is 'pure'
+ del self.distribution.ext_modules[:]
+ try:
+ bdist_wheel = self.get_finalized_command("bdist_wheel")
+ except DistutilsModuleError:
+ # 'bdist_wheel' command not available as wheel is not installed
+ pass
+ else:
+ bdist_wheel.root_is_pure = True
+ log.error('error: building extensions failed: %s' % e)
+
+cmdclass = {"release": release}
+
+if ext_modules:
+ cmdclass["build_ext"] = cython_build_ext
+
+
+setup_params = dict(
name="fonttools",
- version="3.44.0",
+ version="4.22.0",
description="Tools to manipulate font files",
author="Just van Rossum",
author_email="just@letterror.com",
@@ -361,12 +450,14 @@ setup(
url="http://github.com/fonttools/fonttools",
license="MIT",
platforms=["Any"],
+ python_requires=">=3.6",
long_description=long_description,
package_dir={'': 'Lib'},
packages=find_packages("Lib"),
include_package_data=True,
data_files=find_data_files(),
- setup_requires=wheel + bumpversion,
+ ext_modules=ext_modules,
+ setup_requires=setup_requires,
extras_require=extras_require,
entry_points={
'console_scripts': [
@@ -376,8 +467,10 @@ setup(
"pyftmerge = fontTools.merge:main",
]
},
- cmdclass={
- "release": release,
- },
+ cmdclass=cmdclass,
**classifiers
)
+
+
+if __name__ == "__main__":
+ setup(**setup_params)
diff --git a/tox.ini b/tox.ini
index 6a01501f..bcbeeedd 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,13 +1,22 @@
[tox]
minversion = 3.0
-envlist = py{27,37}-cov, htmlcov
+envlist = mypy, py3{6,7,8,9}-cov, htmlcov
+skip_missing_interpreters=true
[testenv]
+setenv =
+ cy: FONTTOOLS_WITH_CYTHON=1
+# use 'download = true' to have tox install the latest pip inside the virtualenv.
+# We need this to be able to install skia-pathops on Linux, which uses a
+# relatively recent 'manylinux2014' platform tag.
+# https://github.com/tox-dev/tox/issues/791#issuecomment-518713438
+download = true
deps =
cov: coverage>=4.3
pytest
pytest-randomly
-rrequirements.txt
+ !nolxml: lxml==4.6.1
extras =
ufo
woff
@@ -15,8 +24,10 @@ extras =
interpolatable
!nolxml: lxml
commands =
+ cy: python -c "from fontTools.cu2qu.cu2qu import COMPILED; assert COMPILED"
+ !cy: python -c "from fontTools.cu2qu.cu2qu import COMPILED; assert not COMPILED"
# test with or without coverage, passing extra positonal args to pytest
- cov: coverage run --parallel-mode -m pytest {posargs:Tests fontTools}
+ cov: coverage run --parallel-mode -m pytest {posargs}
!cov: pytest {posargs:Tests fontTools}
[testenv:htmlcov]
@@ -27,6 +38,13 @@ commands =
coverage combine
coverage html
+[testenv:mypy]
+deps =
+ -r dev-requirements.txt
+skip_install = true
+commands =
+ mypy
+
[testenv:codecov]
passenv = *
deps =